libc ++에서 짧은 문자열 최적화의 메커니즘은 무엇입니까?


102

이 답변 은 짧은 문자열 최적화 (SSO)에 대한 멋진 고급 개요를 제공합니다. 그러나 실제로 libc ++ 구현에서 어떻게 작동하는지 더 자세히 알고 싶습니다.

  • SSO 자격을 얻으려면 문자열이 얼마나 짧아야합니까? 이것은 대상 아키텍처에 따라 달라 집니까?

  • 구현시 문자열 데이터에 액세스 할 때 짧은 문자열과 긴 문자열을 어떻게 구분합니까? m_size <= 16다른 멤버 변수의 일부인 플래그 만큼 간단 합니까? ( m_size또는 그 일부가 문자열 데이터를 저장하는 데 사용될 수도 있다고 생각합니다 ).

SSO를 사용한다는 것을 알고 있기 때문에 libc ++에 대해 특별히이 질문을했습니다. 이것은 libc ++ 홈페이지 에 언급되어 있습니다.

출처를 살펴본 후 몇 가지 관찰 사항은 다음과 같습니다. .

libc ++는 문자열 클래스에 대해 약간 다른 두 가지 메모리 레이아웃으로 컴파일 할 수 있습니다 _LIBCPP_ALTERNATE_STRING_LAYOUT. 이것은 플래그 에 의해 관리됩니다 . 두 레이아웃 모두 리틀 엔디안 머신과 빅 엔디안 머신을 구분하므로 총 4 가지 변형이 있습니다. 나는 다음에서 "일반적인"레이아웃과 리틀 엔디안을 가정 할 것이다.

추가 size_type로 4 바이트이고 value_type1 바이트 라고 가정하면 다음은 문자열의 처음 4 바이트가 메모리에서 보이는 것과 같습니다.

// short string: (s)ize and 3 bytes of char (d)ata
sssssss0;dddddddd;dddddddd;dddddddd
       ^- is_long = 0

// long string: (c)apacity
ccccccc1;cccccccc;cccccccc;cccccccc
       ^- is_long = 1

짧은 문자열의 크기는 상위 7 비트에 있으므로 액세스 할 때 이동해야합니다.

size_type __get_short_size() const {
    return __r_.first().__s.__size_ >> 1;
}

마찬가지로, 긴 문자열의 용량에 대한 getter 및 setter __long_maskis_long비트 를 처리 하는 데 사용 합니다 .

나는 여전히 내 첫 번째 질문에 대한 답을 찾고 있습니다. __min_cap , 짧은 문자열의 용량이 다른 아키텍처에 대해 까요?

기타 표준 라이브러리 구현

이 답변std::string다른 표준 라이브러리 구현 의 메모리 레이아웃에 대한 멋진 개요를 제공합니다 .


libc ++는 오픈 소스이므로 여기 에서 string헤더를 찾을 수 있습니다 . 지금 확인 중입니다. :)
Matthieu M.


@Matthieu M .: 전에는 안타깝게도 파일 크기가 매우 큽니다. 확인하는 데 도움을 주셔서 감사합니다.
ValarDohaeris 2014

@Ali : 나는 인터넷 검색에서 이것을 우연히 발견했습니다. 그러나이 블로그 게시물은 SSO의 예시 일 뿐이며 실제로 사용되는 고도로 최적화 된 변형이 아니라고 명시 적으로 설명합니다.
ValarDohaeris 2014

답변:


120

libc ++ basic_stringsizeof모든 아키텍처에서 3 단어 를 갖도록 설계되었습니다 sizeof(word) == sizeof(void*). long / short 플래그와 짧은 형식의 size 필드를 올바르게 분석했습니다.

짧은 문자열의 용량 인 __min_cap은 다른 아키텍처에 대해 어떤 값을 사용합니까?

짧은 형식으로 작업 할 3 개의 단어가 있습니다.

  • 1 비트는 long / short 플래그로 이동합니다.
  • 크기는 7 비트입니다.
  • char1 바이트가 후행 널로 이동 한다고 가정하면 libc ++는 항상 데이터 뒤에 후행 널을 저장합니다.

이것은 짧은 문자열을 저장하기 위해 3 단어에서 2 바이트를 뺀다 capacity().

32 비트 컴퓨터에서는 10 개의 문자가 짧은 문자열에 맞습니다. sizeof (문자열)는 12입니다.

64 비트 시스템에서는 짧은 문자열에 22 개의 문자가 들어갑니다. sizeof (문자열)는 24입니다.

주요 설계 목표는 sizeof(string)내부 버퍼를 최대한 크게 만드는 동시에을 최소화하는 것이 었습니다 . 그 이유는 이동 구성 및 이동 할당을 가속화하는 것입니다. 클수록sizeof 이동 구성 또는 이동 할당 중에 더 많은 단어를 이동해야합니다.

긴 형식은 데이터 포인터, 크기 및 용량을 저장하기 위해 최소 3 단어가 필요합니다. 따라서 짧은 형식을 동일한 3 단어로 제한했습니다. 4 단어 크기가 더 나은 성능을 가질 수 있다고 제안되었습니다. 나는 그 디자인 선택을 테스트하지 않았습니다.

_LIBCPP_ABI_ALTERNATE_STRING_LAYOUT

_LIBCPP_ABI_ALTERNATE_STRING_LAYOUT"긴 레이아웃"이 다음에서 변경되도록 데이터 멤버를 재정렬하는 라는 구성 플래그 가 있습니다.

struct __long
{
    size_type __cap_;
    size_type __size_;
    pointer   __data_;
};

에:

struct __long
{
    pointer   __data_;
    size_type __size_;
    size_type __cap_;
};

이러한 변화의 동기는 __data_ 우선 이 더 나은 정렬로 인해 성능상의 이점이 . 성능상의 이점을 측정하려고 시도했지만 측정하기가 어려웠습니다. 성능이 나빠지지는 않으며 약간 더 나아질 수 있습니다.

플래그는주의해서 사용해야합니다. 다른 ABI이며 실수로 std::string다른 설정으로 컴파일 된 libc ++와 혼합되면 _LIBCPP_ABI_ALTERNATE_STRING_LAYOUT런타임 오류가 발생합니다.

이 플래그는 libc ++ 공급 업체에서만 변경하는 것이 좋습니다.


17
libc ++와 Facebook Folly 사이에 라이센스 호환성이 있는지 확실하지 않지만 FBstring은 크기를 남은 용량 으로 변경하여 추가 문자 (예 : 23)를 저장 하므로 23 자의 짧은 문자열에 대해 null 종료 자 역할을 이중으로 수행 할 수 있습니다. .
TemplateRex

20
@TemplateRex : 영리합니다. 그러나 libc ++를 채택하면 libc ++에서 std :: string에 대해 좋아하는 다른 특성을 포기해야합니다. 기본 구성 string은 모두 0 비트입니다. 따라서 기본 구성이 매우 효율적입니다. 그리고 규칙을 기꺼이 구부리고 싶다면 때로는 무료입니다. 예를 들어 calloc메모리를 사용하고 기본 구성된 문자열로 가득 찬 것으로 선언 할 수 있습니다.
Howard Hinnant 2014

6
아, 0-init은 정말 좋습니다! BTW, FBstring에는 짧은, 중간 및 큰 문자열을 나타내는 2 개의 플래그 비트가 있습니다. 최대 23 자의 문자열에 대해 SSO를 사용하고 최대 254 자 이상의 문자열에 대해 malloc-ed 메모리 영역을 사용하며 COW를 수행합니다 (더 이상 C ++ 11에서는 합법적이지 않음).
TemplateRex

int64 비트 아키텍처에서 클래스를 16 바이트로만 압축 할 수 있도록 크기와 용량을 s에 저장할 수없는 이유는 무엇 입니까?
phuclv

@ LưuVĩnhPhúc : 64 비트에서 2Gb보다 큰 문자열을 허용하고 싶었습니다. 비용은 확실히 더 큽니다 sizeof. 그러나 동시에에 대한 내부 버퍼 char는 14에서 22로 이동하므로 상당히 좋은 이점입니다.
Howard Hinnant 2016

21

의 libc ++ 구현은 조금 복잡하다, 나는 그것의 대체 설계를 무시하고 리틀 엔디안 컴퓨터를 가정합니다 :

template <...>
class basic_string {
/* many many things */

    struct __long
    {
        size_type __cap_;
        size_type __size_;
        pointer   __data_;
    };

    enum {__short_mask = 0x01};
    enum {__long_mask  = 0x1ul};

    enum {__min_cap = (sizeof(__long) - 1)/sizeof(value_type) > 2 ?
                      (sizeof(__long) - 1)/sizeof(value_type) : 2};

    struct __short
    {
        union
        {
            unsigned char __size_;
            value_type __lx;
        };
        value_type __data_[__min_cap];
    };

    union __ulx{__long __lx; __short __lxx;};

    enum {__n_words = sizeof(__ulx) / sizeof(size_type)};

    struct __raw
    {
        size_type __words[__n_words];
    };

    struct __rep
    {
        union
        {
            __long  __l;
            __short __s;
            __raw   __r;
        };
    };

    __compressed_pair<__rep, allocator_type> __r_;
}; // basic_string

참고 : __compressed_pair기본적으로에 최적화 한 쌍의 빈 자료 최적화 일명 template <T1, T2> struct __compressed_pair: T1, T2 {};; 모든 의도와 목적을 위해 일반 쌍으로 간주 할 수 있습니다. 그 중요성 std::allocator은 무국적이므로 비어 있기 때문에 나타납니다 .

좋아, 이건 좀 날것 이니까 역학을 확인 해보자! 내부적으로 많은 함수는 문자열이 또는 표현을 사용하고 있는지 확인하기 위해 __get_pointer()자체적으로 호출하는 함수를 호출 __is_long합니다 .__long__short

bool __is_long() const _NOEXCEPT
    { return bool(__r_.first().__s.__size_ & __short_mask); }

// __r_.first() -> __rep const&
//     .__s     -> __short const&
//     .__size_ -> unsigned char

솔직히 말해서 이것이 표준 C ++인지 너무 확신하지 못합니다 (초기 하위 시퀀스 프로비저닝을 union알고 있지만 익명 공용체 및 별칭이 함께 던져지는 방법을 모릅니다), 표준 라이브러리는 정의 된 구현을 활용할 수 있습니다. 어쨌든 행동.


이 자세한 답변에 감사드립니다! 내가 놓친 유일한 부분 __min_cap은 다른 아키텍처에 대해 평가할 사항입니다. 무엇 sizeof()을 반환할지, 앨리어싱의 영향을 받는지 잘 모르겠습니다 .
ValarDohaeris 2014

1
@ValarDohaeris 구현이 정의되었습니다. 일반적 3 * the size of one pointer으로이 경우에는 32 비트 아치에서 12 옥텟, 64 비트 아치에서 24 옥텟이 예상 됩니다.
저스틴
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.