提问人:Raedwald 提问时间:4/25/2012 最后编辑:CommunityRaedwald 更新时间:8/29/2019 访问量:44071
首字母缩略词 SSO 在 std::string 上下文中的含义
Meaning of acronym SSO in the context of std::string
问:
在关于优化和代码风格的 C++ 问题中,有几个答案在优化 的副本的上下文中提到了“SSO”。在这种情况下,SSO 意味着什么?std::string
显然不是“单点登录”。也许是“共享字符串优化”?
答:
SSO 是“Small String Optimization”的缩写,这是一种将小字符串嵌入到字符串类的主体中而不是使用单独分配的缓冲区的技术。
背景/概述
对自动变量(“来自堆栈”,即您在不调用 / 的情况下创建的变量)的操作通常比涉及可用存储(“堆”,即使用 创建的变量)的操作快得多。但是,自动数组的大小在编译时是固定的,但自由存储中的数组的大小不是固定的。此外,堆栈大小是有限的(通常为几 MiB),而免费存储仅受系统内存的限制。malloc
new
new
SSO 是短/小字符串优化。A 通常将字符串存储为指向可用存储(“堆”)的指针,这提供了与调用 .这可以防止非常大的字符串的堆栈溢出,但速度可能会变慢,尤其是在执行复制操作时。作为优化,许多实现都会创建一个小型自动数组,例如 .如果字符串的长度不超过 20 个字符(在此示例中,实际大小会有所不同),则会将其直接存储在该数组中。这完全避免了打电话的需要,从而加快了速度。std::string
new char [size]
std::string
char [20]
new
编辑:
我没想到这个答案会如此受欢迎,但既然如此,让我给出一个更现实的实现,但需要注意的是,我从未真正读过任何“野外”SSO的实现。
实施细节
至少需要存储以下信息:std::string
- 尺寸
- 容量
- 数据的位置
大小可以存储为 a 或指向 end 的指针。唯一的区别是,您是希望在用户调用时减去两个指针,还是在用户调用时将 a 添加到指针。容量也可以以任何一种方式存储。std::string::size_type
size
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 位系统,这通常意味着每个字符串有 24 个字节的“开销”,再加上另外 16 个字节用于 SSO 缓冲区(由于填充要求,此处选择了 16 个字节而不是 20 个字节)。存储这三个数据成员加上一个本地字符数组是没有意义的,就像我的简化示例一样。如果 ,那么我会把所有数据都放进去,所以我已经知道容量了,我不需要指向数据的指针。如果 ,那么我不需要 .在我需要所有这些的地方绝对没有重叠。一个不浪费空间的更智能的解决方案看起来更像是这样(未经测试,仅用于示例目的):std::string
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;
};
};
我认为大多数实现看起来更像是这样。
评论
std::string const &
正如其他答案已经解释的那样,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/null
stderr
我已经用我的MacBook和Ubuntu机器的输出创建了图表。 请注意,当长度达到给定点时,复制字符串的时间会有很大的跳跃。 这时,字符串不再适合内部缓冲区,必须使用内存分配。
另请注意,在 linux 机器上,当字符串长度达到 16 时会发生跳转。 在 macbook 上,当长度达到 23 时就会发生跳跃。这确认了 SSO 取决于平台实现。
评论
std::string