출처: https://zenn.dev/hryoichi/articles/f7d097f8cea542

결론

컴파일러의 최적화는 우수.

그냥 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;
}

적당하다고는 해도 아래 사항에 주의했다.

string변수를 생성하는 처리는 비교적 무겁기 때문에 string에 다소 불리한 프로그램이지만 결과적으로는 이 프로그램에서 문제 없을 것 같다.

최적화 옵션

gcc에서는 최적화 옵션을 -O 지정하여 최적화 정도를 지정할 수 있다.

-O0 를 지정하면 최적화되지 않고, -O1, -O2, -O3와 숫자가 커질수록 최적화가 강해진다.

측정 결과

최적화 옵션

문자열

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++에서는 온순하게 std::string를 이용해도 문제 없을 것 같다.

그러나 내 환경에서 최적화 옵션을 지정하지 않으면 -O0 를 지정한 경우와 동일한 속도였다. 디폴트는 최적화되지 않기 때문에 명시적으로 최적화 옵션을 붙일 필요가 있다.