Какова механика оптимизации коротких строк в libС++?
Этот ответ дает хороший общий обзор оптимизации коротких строк (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
в других реализациях стандартной библиотеки.
Ответы
Ответ 1
libС++ basic_string
предназначен для sizeof
3 слов на всех архитектурах, где sizeof(word) == sizeof(void*)
. Вы правильно расчленили длинный/короткий флаг и поле размера в короткой форме.
какое значение __min_cap, емкость коротких строк, берет для разных архитектур?
В короткой форме есть 3 слова для работы с:
- 1 бит переходит к длинному/короткому флагом.
- 7 бит идет на размер.
- Предполагая
char
, 1 байт переходит в конечный нуль (libС++ всегда сохраняет конечный нуль за данными).
Это оставляет 3 слова минус 2 байта для хранения короткой строки (т.е. наибольшей capacity()
без выделения).
На 32-битной машине 10 коротких строк будут вписываться в короткую строку. sizeof (string) - 12.
На 64-битной машине в короткую строку будет вписываться 22 символа. sizeof (string) - 24.
Основная цель проекта заключалась в том, чтобы свести к минимуму sizeof(string)
, сделав как можно больше внутренний буфер. Обоснование - ускорить перемещение конструкции и переместить назначение. Чем больше значение sizeof
, тем больше слов вы должны перемещать во время перемещения или перемещения.
В длинной форме требуется минимум 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, и если случайно смешанный с libС++ std::string
, скомпилированный с другим параметром _LIBCPP_ABI_ALTERNATE_STRING_LAYOUT
, создаст ошибки времени выполнения.
Я рекомендую, чтобы этот флаг был изменен только поставщиком libС++.
Ответ 2
libС++-реализация немного сложна, я проигнорирую ее альтернативный дизайн и предположим маленький конечный компьютер:
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
Честно говоря, я не уверен, что это Standard С++ (я знаю начальное положение подпоследовательности в union
, но не знаю, как он соединяется с анонимным объединением и сглаживанием вместе), но стандартная библиотека разрешена чтобы в любом случае воспользоваться определением, определяемым реализацией.