c++ 17부터는 문자열을 효율적으로 사용하기 위해 std::string_view라는 클래스가 추가되었다.
string_view가 무엇인지 알아보자.
일단, string_view를 알려면 std::string의 복사에 대해 알아보아야 한다.
void Function(std::string _Str)
{
std::cout << _Str << "\n";
}
int main()
{
std::string Test = "ABCD";
Function(Test);
}
위와 같이, Function을 호출하면 어떻게 될까?
당연히 ABCD는 정상적으로 콘솔 창에 출력이 된다.
하지만, 인자로 std::string을 넘기는 과정에서 복사가 이루어진다.
문자열이 짧을 땐 큰 체감이 안될지 몰라도, 문자열이 길어지면 복사 자체도 오래걸리고 SSO의 최적화를 받지 못하기 때문에 임시객체에서 동적할당까지 이루어져서 성능의 저하가 심각해진다.
이러한 복사를 줄이기 위해, 우리는 보통 아래와 같이 참조형을 사용하게 된다.
void Function(std::string& _Str)
{
std::cout << _Str << "\n";
}
int main()
{
std::string Test = "ABCD";
Function(Test);
}
하지만, 여기에도 문제가 생긴다.
바로 리터럴 문자열을 인자로 받을 때이다.
리터럴 문자열은 다른 변수들처럼 스택이나 힙에 저장되지 않고, 데이터영역에서도 문자열을 관리하는 특수한 영역에서 관리된다. 리터럴 문자열은 최적화를 위해 한 번 생성되면 데이터 영역에 저장해뒀다가 나중에 또 그 문자열이 사용될 때 해당 문자열을 참조만 하는 방식으로 관리되고 있다. 그렇기 때문에 리터럴 문자열은 외부에서 함부로 수정해서는 안된다. 그래서 const를 항상 함께 사용해야 하는데, 위의 방식은 const가 붙어있지 않아 리터럴 문자열을 사용할 수가 없다.
void Function(const std::string& _Str)
{
std::cout << _Str << "\n";
}
int main()
{
std::string Test = "ABCD";
Function(Test);
}
그럼 이렇게 const를 붙혀준다면?
리터럴 문자열을 사용할 수는 있지만 여전히 한가지 문제가 남아있다.
위와 같이 사용하면 리터럴 문자열에 대해서는 복사가 여전히 진행되어 버리는 것이다.
참조를 하기 위해선 기본적으로 자료형이 같아야 한다. 하지만, 리터럴 문자열은 const char* 타입이고, Function의 파라미터는 std::string 이다. 이 때문에, 인자로 들어온 리터럴 문자열은 생성자의 인자로 취급되고 임시 객체를 생성하게 된다. 이후, 임시 객체는 문자열을 복사하게 된다. 문자열의 길이가 길다면 동적으로 메모리를 할당하는 작업 또한 실행할 것이다. 그리고 파라미터의 _Str은 해당 임시객체를 참조하게 된다.
불필요한 문자열의 복사를 막으려고 const 참조를 사용했는데, 막지 못하는 상황이 되어버리는 것이다.
이를 방지하기 위해 사용하는 것이 std::string_view 클래스이다.
std::string_view는 내부적으로 문자열에 대한 포인터를 담고 있다. 생성자로 리터럴 문자열이 들어온다면, 해당 문자열의 주소값만을 내부에 보관하게 되기 때문에 문자열의 복사가 발생하지 않는다.
그러므로, 아래와 같이 std::string_view를 활용하면 성능 향상을 노려볼 수 있다.
void Function(std::string_view _Str)
{
std::cout << _Str << "\n";
}
int main()
{
std::string Test = "ABCD";
Function(Test);
}
본인은 string과 string_view의 차이에 대해 4가지의 경우로 시간을 직접 재보았다.
1. 함수의 파라미터는 const std::string& 으로 받고, 리터럴 문자열을 인수로 사용하는 경우
(500만번 반복문 기준 5.06초 소요)
2. 함수의 파라미터는 const std::string& 으로 받고, std::string을 인수로 사용하는 경우
(500만번 반복문 기준 0.017초 소요)
3. 함수의 파라미터는 std::string_view 으로 받고, 리터럴 문자열을 인수로 사용하는 경우
(500만번 반복문 기준 0.28초 소요)
4. 함수의 파라미터는 std::string_view 으로 받고, std::string을 인수로 사용하는 경우
(500만번 반복문 기준 0.15초 소요)
이중 가장 빠른 것은 2번이었다. 그냥 압도적으로 빠르다. 500만번의 반복문을 돌리면 평균적으로 0.017초 정도 소요되었다.
2번의 경우 자료형이 완전히 일치하기 때문에, 임시객체를 생성하지도 않고 그냥 참조만 하게 된다.
그렇기 때문에, string 자체를 인자로 보낼 것이 확실한 상황이라면 const std::string&를 사용하는 것이 가장 효율적인 것 같다.
다음으로 빠른 것은 4번인데, 사실 3번보다 4번이 왜 더 빠른지는 잘 모르겠다. 내 예상이지만, 리터럴 문자열은 주소를 찾아가는 과정이 필요하지만, std::string의 경우 주소값을 내부에 보관하고 있어서 바로 복사가 가능하기 때문이 아닐까 싶다.
중요한 것은 리터럴 문자열을 사용할 때, const std::string& 을 사용하는 것과 std::string_view를 사용하는 것의 차이이다.
속도 차이가 무려 18배나 난다. 어마어마하게 차이나는 것이다.
리터럴 문자열을 자주 사용할 것 같다면, 반드시 std::string_view를 사용하여 최적화를 노려보도록 하자..
'C++ > C++' 카테고리의 다른 글
C++ - 가변 인자 템플릿 (0) | 2024.04.18 |
---|---|
C++ - 코루틴 (coroutine) (0) | 2024.04.14 |
C++ - SSO (Small String Optimization) (0) | 2024.04.11 |
C++ - placement new (0) | 2024.04.10 |
C++ - malloc/free , new/delete (0) | 2024.04.10 |