提问人:ValarDohaeris 提问时间:2/11/2014 最后编辑:ValarDohaeris 更新时间:2/8/2019 访问量:59750
libc++ 中短字符串优化的机制是什么?
What are the mechanics of short string optimization in libc++?
问:
这个答案给出了短字符串优化 (SSO) 的一个很好的高级概述。但是,我想更详细地了解它在实践中是如何工作的,特别是在 libc++ 实现中:
字符串必须有多短才能获得 SSO 的资格? 这是否取决于目标架构?
实现如何区分短期和长期 访问字符串数据时的字符串?它是像其他成员变量一样简单,还是作为其他成员变量一部分的标志?(我 想象一下,它的一部分也可能用于存储 字符串数据)。
m_size <= 16
m_size
我专门针对 libc++ 问了这个问题,因为我知道它使用 SSO,这甚至在 libc++ 主页上都有提及。
以下是查看来源后的一些观察结果:
libc++ 可以使用字符串类的两种略有不同的内存布局进行编译,这由 flag 控制。这两种布局还区分了 little-endian 和 big-endian 机器,这给我们总共留下了 4 种不同的变体。在下文中,我将假设“正常”布局和 little-endian。_LIBCPP_ALTERNATE_STRING_LAYOUT
进一步假设这是 4 个字节,即 1 个字节,这就是字符串的前 4 个字节在内存中的样子:size_type
value_type
// 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_mask
is_long
我仍在寻找第一个问题的答案,即短字符串的容量对于不同的架构会有什么价值?__min_cap
其他标准库实现
这个答案很好地概述了其他标准库实现中的内存布局。std::string
答:
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
好吧,这是相当原始的,所以让我们检查一下机制!在内部,许多函数将调用 which 本身调用以确定字符串是否使用 or 表示形式:__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()
3 * the size of one pointer
libc++ 被设计为在所有架构上都有 3 个词,其中 .您已经正确地剖析了长/空标志,以及简写形式中的大小字段。basic_string
sizeof
sizeof(word) == sizeof(void*)
__min_cap,即短字符串的容量,对不同的架构有什么价值?
在简短的形式中,有 3 个词可以使用:
- 1 位用于多头/空头标志。
- 7 位进入大小。
- 假设 ,1 个字节进入尾随空值(libc++ 将始终在数据后面存储尾随空值)。
char
这留下了 3 个单词减去 2 个字节来存储一个短字符串(即没有分配的最大字符串)。capacity()
在 32 位机器上,短字符串中可容纳 10 个字符。sizeof(string) 的数据类型为 12。
在 64 位计算机上,短字符串中可容纳 22 个字符。sizeof(string) 的数据类型为 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,如果不小心与使用不同设置编译的 libc++ 混合,将产生运行时错误。std::string
_LIBCPP_ABI_ALTERNATE_STRING_LAYOUT
我建议仅由 libc++ 供应商更改此标志。
评论
string
calloc
int
sizeof
char
评论
string