✈️

[C++] char*とstd::stringのアクセス速度と最適化の影響

2024/05/08に公開

TL;DR

コンパイラの最適化は優秀。おとなしくstd::stringを使おう。
ただし、明示的な最適化オプションの指定は必須。

motivation

C言語で書かれたプログラムをC++に書き換える際に、char*で扱っている文字列をstd::stringで扱うようにした。
安全性・利便性の観点ではC++ならstd::string一択だが、実行速度はchar*の方が速そう。実際のところどれほど影響が出るかを調べておきたかった。

ファイルから読み込んだ文字列を1文字単位で読んでいくプログラムだったので、文字列に対してなにか操作をするというよりは各文字にアクセスする時間を計測する。

テストコード

計測のため以下のコードを用意した。

#include <iostream>
#include <chrono>
#include <string>

using namespace std;

int main()
{
	chrono::high_resolution_clock::time_point sstart, send, cstart, cend;
	long long l;
	l = 0;
	sstart = chrono::high_resolution_clock::now();
	for (size_t i = 0; i < 10000000; i++)
	{
		string s = "Hello, world!";
		for (size_t i = 0; i < s.length(); i++)
		{
			if (s[i] == 'l')
			{
				l += 3;
			}
			else
			{
				l++;
			}
		}
	}
	send = chrono::high_resolution_clock::now();

	cout << l << endl;
	double stime = static_cast<double>(chrono::duration_cast<chrono::microseconds>(send - sstart).count() / 1000.0);
	printf("string : %lf[ms]\n", stime);

	l = 0;
	cstart = chrono::high_resolution_clock::now();
	for (size_t i = 0; i < 10000000; i++)
	{
		char *c = "Hello, world!";
		while (*c)
		{
			if (*c == 'l')
			{
				l += 3;
			}
			else
			{
				l++;
			}
			c++;
		}
	}
	cend = chrono::high_resolution_clock::now();

	cout << l << endl;
	double ctime = static_cast<double>(chrono::duration_cast<chrono::microseconds>(cend - cstart).count() / 1000.0);
	printf("char* : %lf[ms]\n", ctime);
	return 0;
}

適当とはいえ以下の点に注意した。

  • if文以下の処理は適当だが、計算結果が一度も利用されない変数の計算はコンパイラの最適化で消されそうなので一応終了時間測定後に計算結果を出力している。

  • char*で一文字ずつ読み進めた後、最初の文字に戻るという通常は発生しないであろう処理にかかる時間を計測する意味を見出せなかったためループ内で毎回文字列の変数を定義している。これに合わせてstringの場合も毎回変数を生成している。

最適化オプション

gccでは、最適化オプション-Oを指定することで最適化の程度を指定できる。
-O0を指定すると最適化されなくなり、-O1-O2-O3と数字が大きくなるほど最適化が強くなる。

計測結果

最適化オプション string char*
-O0 451.056000[ms] 127.673000[ms]
-O1 45.933000[ms] 42.826000[ms]
-O2 42.826000[ms] 51.897000[ms]
-O3 0.000000[ms] 41.898000[ms]

結論

コンパイラによる最適化が一切ない場合はさすがにstringの遅さが目立つが、標準的な最適化で既に誤差の範囲に高速化され、それ以上の最適化ではむしろchar*でアクセスするより早い結果になっている。
-O3に関しては最適化されすぎて無意味なループをさせているのがコンパイラにバレてしまった気がするので計測としては適切でないかも。

いずれにせよ、少なくとも1文字ずつアクセスする場合にstringを使った場合はコンパイラの最適化が強く働き結果的にchar*と同等以上の速度を出せることがわかる。
また、今回は調べていないが諸々の操作も標準で高速に行える(例えば[c++]stringのコピーが遅かった件

速度を気にする場合もC++ではおとなしくstd::stringを利用して問題なさそう。
ただし、私の環境では最適化オプションを指定しない場合、-O0を指定した場合と同じ速度だった。デフォルトは最適化されないようなので明示的に最適化オプションをつける必要がある。

Discussion