본문 바로가기
개발/C++ (98,03,11,14,17,20,23)

Modern C++ : Small String Optimization (SSO)

by snowoods 2025. 8. 31.

Modern C++

Small String Optimization (SSO)

 

개요

Small String Optimization(SSO)은 std::string 클래스에서 짧은 문자열을 저장할 때 힙 할당을 피하고 스택 메모리를 활용하여 성능을 최적화하는 기법입니다. 이 기법은 문자열의 길이가 특정 임계값(SSO capacity) 이내일 때 적용됩니다.

 

C++ 버전별 주요 키워드 도입 시기

  • C++98: std::string의 구현 세부사항으로 SSO가 도입되기 시작 (표준화된 사양은 아님)
  • C++11: 이동 생성자/대입 연산자와 함께 SSO 최적화가 더욱 중요해짐
  • C++17: std::string_view 도입으로 SSO와 함께 사용할 때의 최적화 기회 증가

 

내용 설명

SSO는 짧은 문자열을 다룰 때 다음과 같은 이점을 제공합니다:

  1. 힙 할당 제거: 짧은 문자열은 객체 자체에 저장되어 동적 메모리 할당이 발생하지 않음
  2. 캐시 지역성 향상: 스택에 저장된 데이터는 캐시 적중률이 높아짐
  3. 메모리 단편화 감소: 작은 메모리 할당/해제가 빈번하게 발생하는 것을 방지

주요 컴파일러별 SSO 구현 현황:

  • MSVC: 15바이트 SSO 적용, sizeof(std::string) = 32바이트
  • GCC: 15바이트 SSO 적용, sizeof(std::string) = 32바이트
  • Clang: 22바이트 SSO 적용, sizeof(std::string) = 24바이트

 

예제 코드

#include <iostream>
#include <string>

// std::string의 SSO 여부를 확인하는 함수
void checkSSO(const std::string& s) {
    std::cout << "문자열: \"" << s << "\" (길이: " << s.length() << ")" << std::endl;
    std::cout << "데이터 주소: " << (void*)s.data() << std::endl;
    std::cout << "객체 주소: " << (void*)&s << std::endl;

    // 데이터 주소가 객체 내부에 있는지 확인 (SSO 여부 판단)
    if ((void*)s.data() >= (void*)&s && 
        (void*)s.data() < (void*)((char*)&s + sizeof(s))) {
        std::cout << "SSO가 적용되었습니다.\n";
    } else {
        std::cout << "힙에 할당되었습니다.\n";
    }
    std::cout << "----------------------------------\n";
}

int main() {
    // 짧은 문자열 (SSO 적용)
    std::string shortStr = "Hello, SSO!";

    // 긴 문자열 (힙 할당)
    std::string longStr = "This is a very long string that will not use SSO and will be allocated on the heap";

    checkSSO(shortStr);
    checkSSO(longStr);

    // 문자열 길이에 따른 SSO 여부 확인
    for (int i = 0; i <= 30; ++i) {
        std::string s(i, 'x');
        std::cout << "길이 " << i << ": " << (s.capacity() > 15 ? "힙 할당" : "SSO") << std::endl;
    }

    return 0;
}

 

실행 결과 (예시)

문자열: "Hello, SSO!" (길이: 11)
데이터 주소: 0x7ffc7a3b1c70
객체 주소: 0x7ffc7a3b1c70
SSO가 적용되었습니다.
----------------------------------
문자열: "This is a very long string that will not use SSO and will be allocated on the heap" (길이: 74)
데이터 주소: 0x55a1f9b4eeb0
객체 주소: 0x7ffc7a3b1cb0
힙에 할당되었습니다.
----------------------------------
길이 0: SSO
길이 1: SSO
...
길이 15: SSO
길이 16: 힙 할당
...
길이 30: 힙 할당

 

컴파일러별 SSO 바이트 구조

1. Clang(libc++)의 std::string 구조와 24바이트

Clang이 사용하는 libc++ 표준 라이브러리에서 std::string 객체의 크기는 64비트 시스템 기준으로 24바이트입니다. 이 24바이트는 std::string 객체가 항상 차지하는 공간이며, 문자열 길이에 따라 두 가지 용도로 사용됩니다.

  • 긴 문자열 (Long String): 문자열이 SSO 버퍼에 담을 수 없을 만큼 길 때, 힙(heap)에 메모리를 동적으로 할당합니다. 이때 24바이트는 다음과 같이 사용됩니다.
    • capacity (8바이트): 할당된 메모리의 전체 크기
    • size (8바이트): 실제 문자열의 길이
    • data (8바이트): 힙에 할당된 메모리를 가리키는 포인터
  • 짧은 문자열 (Short String - SSO): 문자열이 짧을 때, 이 24바이트 공간을 직접 문자열 데이터 저장에 사용합니다.
    • 데이터 저장 공간: 23바이트
    • 제어(Control) 정보: 1바이트

여기서 핵심은 제어 바이트입니다. 이 1바이트는 현재 문자열이 SSO 상태인지, 그리고 실제 문자열의 길이가 얼마인지를 저장하는 데 사용됩니다. 나머지 23바이트는 문자열 데이터를 저장합니다. C-스타일 문자열과의 호환성을 위해 마지막에는 항상 널 종료 문자(\0)가 들어가야 하므로, 실제 사용자가 저장할 수 있는 문자는 최대 22자가 됩니다. (23바이트 - 1바이트 \0 = 22바이트) 결론: std::string 객체 자체의 크기가 24바이트이며, SSO가 적용될 때 이 공간을 문자 데이터와 제어 정보를 담는 데 재활용하는 것입니다. 따라서 22자의 문자열을 저장해도 객체의 크기는 24바이트로 유지됩니다.

2. 다른 컴파일러(GCC, MSVC)의 경우

다른 컴파일러의 표준 라이브러리 구현체들도 비슷한 원리를 따르지만, 내부 구조가 조금씩 다릅니다.

  • GCC (libstdc++): 64비트 시스템에서 std::string 크기는 32바이트입니다. SSO로 저장 가능한 문자열은 최대 15자입니다. 이는 libstdc++가 문자열 길이(size)와 데이터 포인터(data) 멤버를 항상 유지하고, 나머지 16바이트를 SSO 버퍼로 사용하기 때문입니다. (16바이트 - 1바이트 \0 = 15자)
  • MSVC (Microsoft Visual C++): 64비트 시스템에서 std::string 크기는 32바이트입니다. SSO는 최대 15자까지 지원하며, GCC와 유사한 구조를 가집니다.

이처럼 모든 주요 컴파일러들이 SSO를 구현하지만, 객체의 전체 크기와 SSO로 저장 가능한 문자 수는 구현 방식에 따라 차이가 있습니다.

3. 문자 인코딩 (UTF-8)과의 관계

std::string은 기본적으로 char의 시퀀스를 다룹니다. char는 1바이트 크기의 자료형이죠. std::string은 이 바이트들의 나열을 저장할 뿐, 그 내용이 어떤 인코딩(ASCII, UTF-8 등)을 따르는지는 해석하지 않습니다.

  • UTF-8과 std::string: UTF-8은 가변 길이 인코딩으로, 영문자나 숫자는 1바이트로, 한글과 같은 문자는 주로 3바이트로 표현됩니다. std::string에 UTF-8 문자열을 저장하면, 각 문자를 구성하는 바이트들이 그대로 저장됩니다.
    • 예시: "abc"는 3바이트를 차지합니다.
    • 예시: "가나다"는 9바이트를 차지합니다. (한 글자당 3바이트)

따라서 Clang의 SSO(최대 22자)는 정확히 말해 22바이트를 의미합니다.

  • 영문, 숫자, 기호 등 1바이트 문자는 22개까지 저장할 수 있습니다.
  • 한글과 같은 3바이트 문자는 22 / 3 = 7 즉, 7개까지 저장할 수 있습니다.

std::string이 2바이트 문자를 특별히 취급하는 것은 아니며, UTF-8 인코딩 체계에 따라 바이트 스트림을 그대로 저장할 뿐입니다.

 

활용팁

  1. 성능이 중요한 루프에서 문자열 사용 시: 짧은 문자열을 자주 생성/소멸하는 경우 SSO 덕분에 성능 저하가 크지 않음
  2. 문자열 저장 공간 예측: SSO 한계를 알고 있다면 문자열 길이를 제한하여 성능 최적화 가능
  3. 문자열 전달 시: const std::string& 대신 std::string_view를 사용하면 SSO와 무관하게 효율적으로 전달 가능
  4. 메모리 레이아웃 고려: SSO로 인해 std::string의 크기가 플랫폼마다 다를 수 있으므로, 메모리 레이아웃이 중요한 코드에서는 주의 필요
  5. 문자열 길이 모니터링: s.capacity()를 사용하여 현재 문자열의 메모리 할당 방식을 확인할 수 있음