提问者:小点点

LibC++中短字符串优化的机制是什么?


这个答案给出了短字符串优化(SSO)的一个很好的高级概述。但是,我想更详细地了解它在实践中是如何工作的,特别是在libc++实现中:

>

在访问字符串数据时,实现如何区分短字符串和长字符串?它是像那么简单,还是作为某个其他成员变量的一部分的标志?(我想或它的一部分也可能用于存储字符串数据。

我是专门针对libc++提出这个问题的,因为我知道它使用SSO,甚至在libc++主页上也提到了这个问题。

下面是看过出处后的一些观察:

LibC++可以使用string类的两种略有不同的内存布局进行编译,这是由标志控制的。这两种布局还区分了小端和大端机器,这给我们留下了总共4种不同的变体。在下面的内容中,我将假定“正常”布局和小endpoint。

进一步假设为4个字节,为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;
}

类似地,长字符串容量的getter和setter使用处理位。

我仍然在寻找我的第一个问题的答案,即,短字符串的容量,对于不同的体系结构会取什么值?

其他标准库实现

这个答案很好地概述了其他标准库实现中的内存布局。


共2个答案

匿名用户

LibC++在所有体系结构上都设计为有一个3个字,其中。您已经正确地分析了long/short标志和short形式的size字段。

对于不同的体系结构,__min_cap(短字符串的容量)会取什么值?

在简短的形式中,有3个单词需要使用:

  • 假设,则1个字节转到尾随null(Libc++将始终在数据后面存储尾随null)./li>

这样就剩下3个字减去2个字节来存储一个短字符串(即没有分配的最大)。

在32位机器上,短字符串中可以容纳10个字符。sizeof(字符串)为12。

在64位机器上,短字符串中可以容纳22个字符。sizeof(字符串)为24。

一个主要的设计目标是最小化,同时使内部缓冲区尽可能大。其基本原理是加快移动构造和移动分配。越大,在move构造或move赋值过程中需要移动的单词就越多。

长表单至少需要3个字来存储数据指针,大小和容量。因此,我将缩写形式限制在那三个相同的单词上。有人建议,4字大小的OF可能具有更好的性能。我没有测试过那个设计选择。

_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_;
};

这种改变的动机是相信将放在首位会由于更好的对齐而具有一些性能优势。曾尝试衡量性能优势,但很难衡量。它不会让性能变差,可能会让它稍微好一点。

这面旗帜应该小心使用。它是一个不同的ABI,如果不小心与使用不同设置编译的libc++混合,将创建运行时错误。

我建议仅由libc++供应商更改此标志。

匿名用户

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

注意:本质上是为空基优化而优化的一对,也就是;struct__compressed_pair:T1,T2/代码;出于所有的意图和目的,你可以认为它是一对普通的。它之所以如此重要,是因为是无状态的,因此是空的。

好吧,这是相当原始的,所以让我们检查一下机械!在内部,许多函数将调用,该函数本身调用以确定字符串使用的是还是表示形式:

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

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

老实说,我不太确定这是标准的C++(我知道中的初始子序列规定,但不知道它是如何与匿名联合和别名相结合的),但无论如何,标准库是允许利用实现定义的行为的。

相关问题


MySQL Query : SELECT * FROM v9_ask_question WHERE 1=1 AND question regexp '(libc++|中短|字符串|优化|机制)' ORDER BY qid DESC LIMIT 20
MySQL Error : Got error 'repetition-operator operand invalid' from regexp
MySQL Errno : 1139
Message : Got error 'repetition-operator operand invalid' from regexp
Need Help?