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

Meaning of acronym SSO in the context of std::string

提问人:Raedwald 提问时间:4/25/2012 最后编辑:CommunityRaedwald 更新时间:8/29/2019 访问量:44071

问:

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

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

C++ 字符串 优化

评论

74赞 jalf 4/25/2012
这只是一个副本,就像“什么是 2+2”是“200 / 50 的结果是什么”的副本一样。答案是一样的。问题完全不同。“重复关闭”是指当多人提出相同的*问题时使用。当一个人问“如何实施”,而另一个人问“SSO是什么意思”时,你必须绝对疯狂地认为它们是同一个问题std::string
3赞 Oliver Charlesworth 4/25/2012
@jalf:如果现有的 Q+A 完全涵盖了这个问题的范围,我会认为它是重复的(我并不是说 OP 应该自己搜索这个,只是说这里的任何答案都将涵盖已经涵盖的领域。
55赞 jalf 4/25/2012
你实际上是在告诉OP“你的问题是错误的。但你需要知道答案,才能知道你应该问什么”。让人们关闭 SO 的好方法。这也使你不必要地难以找到你需要的信息。如果人们不问问题(结束语实际上是在说“这个问题不应该被问”),那么那些还不知道答案的人就没有办法得到这个问题的答案
12赞 Oliver Charlesworth 4/25/2012
@jalf:完全没有。IMO,“投票关闭”并不意味着“坏问题”。我为此投了反对票。我认为这是一个重复,因为所有答案是“未定义行为”的无数问题(i=i++ 等)都是彼此重复的。另一方面,如果它不是重复的,为什么没有人回答这个问题?
10赞 Matthieu M. 4/25/2012
@jalf:我同意 Oli 的观点,这个问题不是重复的,但答案是重复的,因此重定向到另一个答案已经摆放的问题似乎很合适。作为重复项关闭的问题不会消失,相反,它们充当指向答案所在的另一个问题的指针。下一个寻找 SSO 的人最终会来到这里,按照重定向,找到她的答案。

答:

35赞 Mark Ransom 4/26/2012 #1

SSO 是“Small String Optimization”的缩写,这是一种将小字符串嵌入到字符串类的主体中而不是使用单独分配的缓冲区的技术。

281赞 David Stone 4/26/2012 #2

背景/概述

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

SSO 是短/小字符串优化。A 通常将字符串存储为指向可用存储(“堆”)的指针,这提供了与调用 .这可以防止非常大的字符串的堆栈溢出,但速度可能会变慢,尤其是在执行复制操作时。作为优化,许多实现都会创建一个小型自动数组,例如 .如果字符串的长度不超过 20 个字符(在此示例中,实际大小会有所不同),则会将其直接存储在该数组中。这完全避免了打电话的需要,从而加快了速度。std::stringnew char [size]std::stringchar [20]new

编辑:

我没想到这个答案会如此受欢迎,但既然如此,让我给出一个更现实的实现,但需要注意的是,我从未真正读过任何“野外”SSO的实现。

实施细节

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

  • 尺寸
  • 容量
  • 数据的位置

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

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

首先,考虑基于我上面概述的幼稚实现:

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 位系统,这通常意味着每个字符串有 24 个字节的“开销”,再加上另外 16 个字节用于 SSO 缓冲区(由于填充要求,此处选择了 16 个字节而不是 20 个字节)。存储这三个数据成员加上一个本地字符数组是没有意义的,就像我的简化示例一样。如果 ,那么我会把所有数据都放进去,所以我已经知道容量了,我不需要指向数据的指针。如果 ,那么我不需要 .在我需要所有这些的地方绝对没有重叠。一个不浪费空间的更智能的解决方案看起来更像是这样(未经测试,仅用于示例目的):std::stringm_size <= 16m_ssom_size > 16m_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;
    };
};

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

评论

8赞 BillT 9/12/2015
以下是一些实际实现的一个很好的解释: stackoverflow.com/a/28003328/203044
0赞 TonySalimi 5/24/2019
当大多数开发人员使用 const 引用传递 std::string 时,SSO 真的实用吗?
10赞 David Stone 6/3/2019
SSO 除了使复制更便宜之外,还有两个好处。首先,如果字符串大小适合较小的缓冲区大小,则无需在初始构造上进行分配。第二种情况是,当一个函数接受一个 时,获取数据是单个内存的间接,因为数据存储在引用的位置。如果没有小的字符串优化,访问数据将需要两个间接内存(第一个加载对字符串的引用并读取其内容,然后第二个读取字符串中数据指针的内容)。std::string const &
1赞 edwinc 12/22/2022
@TonySalimi:关键是要避免 malloc 和 indirect 在写入/读取时访问 char 数组。所以,是的,它是实用的。当您将该字符串传递给函数并完成读取时,读取速度可能会更快,因为数据将位于堆栈上。
34赞 HugoTeixeira 8/11/2018 #3

正如其他答案已经解释的那样,SSO 意味着小/短字符串优化。 这种优化背后的动机是无可否认的证据表明,应用程序通常比处理较长的字符串处理更多的短字符串。

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

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

基准

这是一个小程序,用于对许多具有相同长度的字符串的复制操作进行基准测试。 它开始打印复制长度 = 1 的 1000 万个字符串的时间。 然后它以长度 = 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/nullstderr

我已经用我的MacBook和Ubuntu机器的输出创建了图表。 请注意,当长度达到给定点时,复制字符串的时间会有很大的跳跃。 这时,字符串不再适合内部缓冲区,必须使用内存分配。

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

Ubuntu的 SSO benchmark on Ubuntu

Macbook Pro的 SSO benchmark on Macbook Pro

评论

1赞 8/18/2020
我通过删除不必要的设计选择,将时间从近 4 分钟缩短到近 1 分钟。ideone.com/KJCh2H