首字母缩写词SSO在std :: string上下文中的含义


155

有关优化和代码风格的C ++问题中,在优化的副本的上下文中,有几个答案称为“ SSO” std::string。在这种情况下,SSO是什么意思?

显然不是“单一登录”。也许是“共享字符串优化”?


57
这只是重复项,与“ what is 2 + 2”和“ what is 200/50”的重复项相同。答案是一样的。问题是完全不同的。当多个人问同一个*问题时,将使用“重复复制”。当一个人问“如何std::string实施”,而另一个人问“ SSO是什么意思”时,您必须绝对疯了,以将他们视为同一个问题
jalf 2012年

1
@jalf:如果现有的Q + A恰好涵盖了这个问题的范围,我会认为它是重复的(我并不是说OP应该自己进行搜索,只是这里的任何答案都将涵盖)
Oliver Charlesworth '04年5

47
您实际上是在告诉OP,“您的问题是错的。但是您需要知道答案才能知道您应该问什么”。关闭人们的好方法。这也使得不必要地很难找到您需要的信息。如果人们不问问题(并且结语实际上是在说“不应该问这个问题”),那么尚不知道答案的人将无法获得问题的答案
jalf 2012年

7
@jalf:一点也不。IMO,“投票关闭”并不意味着“坏问题”。我为此使用downvotes。我认为它是重复的,因为答案为“不确定行为”的所有无数问题(i = i ++等)都是重复的。另一方面,如果不是重复项,为什么没人回答这个问题?
奥利弗·查尔斯沃思

5
@jalf:我同意奥利(Oli)的观点,它不是一个重复的问题,但是答案是正确的,因此将其重定向到另一个已经很合适的问题。由于重复问题而关闭的问题不会消失,而是充当指向答案所在的另一个问题的指针。下一个寻找SSO的人将在这里结束,按照重定向进行操作,并找到她的答案。
Matthieu M.

Answers:


212

背景/概述

自动变量(“从堆栈”,这是您在不调用malloc/的情况下创建的变量new)上的操作通常比涉及免费存储的操作(“堆”,这是使用来创建的变量)要快得多new。但是,自动数组的大小在编译时是固定的,但免费存储中的数组大小不是固定的。此外,堆栈大小受限制(通常为几个MiB),而免费存储空间仅受系统内存的限制。

SSO是短/小字符串优化。std::string通常,A 将字符串存储为指向免费存储区(“堆”)的指针,该存储区具有与调用相似的性能特征new char [size]。这样可以防止非常大的字符串出现堆栈溢出,但是这样做可能会更慢,尤其是对于复制操作而言。作为优化,许多实现会std::string创建一个小的自动数组,例如char [20]。如果您的字符串长度不超过20个字符(在此示例中,实际大小有所不同),它将直接将其存储在该数组中。这完全避免了调用new,从而加快了速度。

编辑:

我没想到这个答案会如此流行,但是既然如此,让我给出一个更现实的实现,但要警告的是,我从未真正阅读过任何“野外”的SSO实现。

实施细节

至少std::string需要存储以下信息:

  • 规模
  • 容量
  • 数据位置

大小可以存储为a std::string::size_type或指向末尾的指针。唯一的区别是您是要在用户调用时减去两个指针,还是在用户调用时size将a添加size_type到指针end。容量也可以以任何一种方式存储。

您不用为不使用的东西付费。

首先,根据我上面概述的内容考虑天真的实现:

class string {
public:
    // all 83 member functions
private:
    std::unique_ptr<char[]> m_data;
    size_type m_size;
    size_type m_capacity;
    std::array<char, 16> m_sso;
};

对于64位系统,通常意味着std::string每个字符串具有24字节的“开销”,另外还有16个用于SSO缓冲区(此处出于填充要求,此处选择16个而不是20个)。正如我的简化示例中那样,存储这三个数据成员以及本地字符数组并没有多大意义。如果为m_size <= 16,则将所有数据放入m_sso,因此我已经知道容量,并且不需要指向数据的指针。如果m_size > 16是的话我不需要m_sso。我需要所有它们的地方绝对没有重叠。一个不浪费空间的更智能的解决方案看起来像这样(未经测试,仅作为示例用途):

class string {
public:
    // all 83 member functions
private:
    size_type m_size;
    union {
        class {
            // This is probably better designed as an array-like class
            std::unique_ptr<char[]> m_data;
            size_type m_capacity;
        } m_large;
        std::array<char, sizeof(m_large)> m_small;
    };
};

我认为大多数实现看起来都像这样。


7
这是一些实际实现的很好的解释:stackoverflow.com/a/28003328/203044
BillT 2015年

当大多数开发人员使用const引用传递std :: string时,SSO真的很实用吗?
古塔

1
除了使复制便宜之外,SSO还有两个好处。首先是,如果您的字符串大小适合较小的缓冲区大小,则不需要在初始构造上进行分配。第二个原因是,当一个函数接受a时std::string const &,获取数据是单个内存间接寻址,因为数据存储在引用的位置。如果没有小的字符串优化,则访问数据将需要两个内存间接调用(首先是加载对字符串的引用并读取其内容,然后是第二个以读取字符串中数据指针的内容)。
大卫·斯通

34

SSO是“小型字符串优化”(Small String Optimization)的缩写,该技术是在字符串类的主体中嵌入小字符串,而不是使用单独分配的缓冲区。


15

正如其他答案所解释的那样,SSO表示小/短字符串优化。这种优化背后的动机是无可否认的证据,即应用程序通常处理的短字符串要比长的字符串多得多。

正如David Stone 在上面的回答中所解释的那样,std::string该类使用内部缓冲区来存储给定长度的内容,这消除了动态分配内存的需要。这使代码更高效更快

这个与之相关的其他答案清楚地表明,内部缓冲区的大小取决于std::string实现,而实现因平台而异(请参见下面的基准测试结果)。

基准测试

这是一个小程序,用于对许多相同长度的字符串的复制操作进行基准测试。它开始打印时间来复制1000万个长度= 1的字符串。然后重复长度= 2的字符串。一直持续到长度为50。

#include <string>
#include <iostream>
#include <vector>
#include <chrono>

static const char CHARS[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
static const int ARRAY_SIZE = sizeof(CHARS) - 1;

static const int BENCHMARK_SIZE = 10000000;
static const int MAX_STRING_LENGTH = 50;

using time_point = std::chrono::high_resolution_clock::time_point;

void benchmark(std::vector<std::string>& list) {
    std::chrono::high_resolution_clock::time_point t1 = std::chrono::high_resolution_clock::now();

    // force a copy of each string in the loop iteration
    for (const auto s : list) {
        std::cout << s;
    }

    std::chrono::high_resolution_clock::time_point t2 = std::chrono::high_resolution_clock::now();
    const auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count();
    std::cerr << list[0].length() << ',' << duration << '\n';
}

void addRandomString(std::vector<std::string>& list, const int length) {
    std::string s(length, 0);
    for (int i = 0; i < length; ++i) {
        s[i] = CHARS[rand() % ARRAY_SIZE];
    }
    list.push_back(s);
}

int main() {
    std::cerr << "length,time\n";

    for (int length = 1; length <= MAX_STRING_LENGTH; length++) {
        std::vector<std::string> list;
        for (int i = 0; i < BENCHMARK_SIZE; i++) {
            addRandomString(list, length);
        }
        benchmark(list);
    }

    return 0;
}

如果要运行此程序,则应这样做,这样./a.out > /dev/null就不会计算打印字符串的时间。重要的数字被打印到stderr,因此它们将显示在控制台中。

我已经使用MacBook和Ubuntu计算机的输出创建了图表。请注意,当长度达到给定点时,复制字符串的时间将大大增加。这是字符串不再适合内部缓冲区且必须使用内存分配的时刻。

另请注意,在linux计算机上,当字符串的长度达到16时发生跳转。在macbook上,当长度达到23时发生跳转。这确认SSO取决于平台的实现。

的Ubuntu Ubuntu上的SSO基准

Macbook Pro Macbook Pro上的SSO基准

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.