Этот ответ дает хороший общий обзор оптимизации коротких строк (SSO). Однако хотелось бы подробнее узнать, как это работает на практике, в частности в реализации libc ++:
Насколько короткой должна быть строка, чтобы иметь право на SSO? Это зависит от целевой архитектуры?
Как реализация различает короткие и длинные строки при доступе к строковым данным? Это так просто,
m_size <= 16
или это флаг, являющийся частью какой-либо другой переменной-члена? (Я полагаю, что этоm_size
или его часть также может использоваться для хранения строковых данных).
Я задал этот вопрос специально для libc ++, потому что знаю, что он использует SSO, об этом даже упоминается на домашней странице libc ++ .
Вот некоторые наблюдения после просмотра источника :
libc ++ может быть скомпилирован с двумя немного разными схемами памяти для строкового класса, это регулируется _LIBCPP_ALTERNATE_STRING_LAYOUT
флагом. Обе схемы также различают машины с прямым порядком байтов и обратным порядком байтов, что оставляет нам в общей сложности 4 различных варианта. В дальнейшем я буду предполагать "нормальную" раскладку и прямой порядок байтов.
Если предположить, что size_type
это 4 байта, а это value_type
1 байт, первые 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;
}
Точно так же геттер и сеттер емкости длинной строки используются __long_mask
для обхода is_long
бита.
Я все еще ищу ответ на свой первый вопрос, т.е. какое значение будет __min_cap
иметь емкость коротких строк для разных архитектур?
Другие реализации стандартной библиотеки
Этот ответ дает хороший обзор std::string
макетов памяти в других реализациях стандартной библиотеки.
источник
string
заголовок здесь , я проверяю его в данный момент :)Ответы:
Библиотека libc ++
basic_string
разработана так, чтобы иметьsizeof
три слова для всех архитектур, гдеsizeof(word) == sizeof(void*)
. Вы правильно рассекли длинный / короткий флажок и поле размера в краткой форме.В краткой форме нужно работать с тремя словами:
char
, что 1 байт идет до конечного нуля (libc ++ всегда будет хранить конечный null за данными).Это оставляет 3 слова минус 2 байта для хранения короткой строки (т. Е. Самой большой строки
capacity()
без распределения).На 32-битной машине в короткую строку уместится 10 символов. sizeof (строка) - 12.
На 64-битной машине в короткую строку уместится 22 символа. sizeof (строка) - 24.
Основная цель дизайна заключалась в том, чтобы свести к минимуму
sizeof(string)
, но сделать внутренний буфер как можно большим. Обоснование состоит в том, чтобы ускорить строительство и переместить назначение. Чем большеsizeof
, тем больше слов вам нужно переместить во время построения перемещения или задания перемещения.В полной форме требуется как минимум 3 слова для хранения указателя данных, размера и емкости. Поэтому я ограничил краткую форму теми же тремя словами. Было высказано предположение, что размер слова 4 может быть лучше. Я не тестировал этот выбор дизайна.
_LIBCPP_ABI_ALTERNATE_STRING_LAYOUT
Называется флаг конфигурации,
_LIBCPP_ABI_ALTERNATE_STRING_LAYOUT
который переупорядочивает элементы данных таким образом, что "длинный макет" изменяется с:кому:
Мотивом для этого изменения является вера в то, что ставка на
__data_
первое место даст некоторые преимущества в производительности за счет лучшего согласования. Была сделана попытка измерить преимущества производительности, и это было трудно измерить. Это не ухудшит производительность, а может немного улучшить.Флаг следует использовать осторожно. Это другой ABI, и если его случайно смешать с libc ++,
std::string
скомпилированным с другим параметром_LIBCPP_ABI_ALTERNATE_STRING_LAYOUT
, возникнут ошибки времени выполнения.Я рекомендую изменять этот флаг только поставщикам libc ++.
источник
string
все 0 бит. Это делает конструкцию по умолчанию суперэффективной. И если вы готовы нарушить правила, иногда даже бесплатно. Например, вы можетеcalloc
запомнить и просто объявить, что он заполнен строками, построенными по умолчанию.int
s, чтобы класс можно было упаковать только до 16 байт на 64-битных архитектурах?sizeof
. Но в то же время внутренний буфер увеличенchar
с 14 до 22, что является неплохим преимуществом.Реализация libc ++ немного сложна, я проигнорирую ее альтернативный дизайн и предположу, что это маленький компьютер с порядком байтов:
Примечание:
__compressed_pair
по сути, это пара, оптимизированная для оптимизации пустой базы , иначеtemplate <T1, T2> struct __compressed_pair: T1, T2 {};
; во всех смыслах и целях вы можете считать его обычной парой. Его важность возникает только потому, что он неstd::allocator
имеет состояния и, следовательно, пуст.Ладно, это довольно сыро, поэтому давайте проверим механику! Внутри многие функции будут вызывать,
__get_pointer()
который сам вызывает,__is_long
чтобы определить, использует ли строка представление__long
или__short
:Честно говоря, я не слишком уверен, что это Стандартный C ++ (я знаю исходное положение подпоследовательности,
union
но не знаю, как оно сочетается с анонимным объединением и псевдонимом, брошенными вместе), но Стандартной библиотеке разрешено использовать преимущества определенной реализации поведение в любом случае.источник
__min_cap
было бы оценить для разных архитектур, я не уверен, чтоsizeof()
вернется и как на это влияет сглаживание.3 * the size of one pointer
в этом случае можно ожидать , что это будет 12 октетов на 32-битной арке и 24 октета на 64-битной арке.