在有关优化和代码风格的C ++问题中,在优化的副本的上下文中,有几个答案称为“ SSO” std::string
。在这种情况下,SSO是什么意思?
显然不是“单一登录”。也许是“共享字符串优化”?
在有关优化和代码风格的C ++问题中,在优化的副本的上下文中,有几个答案称为“ SSO” std::string
。在这种情况下,SSO是什么意思?
显然不是“单一登录”。也许是“共享字符串优化”?
Answers:
自动变量(“从堆栈”,这是您在不调用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;
};
};
我认为大多数实现看起来都像这样。
std::string const &
,获取数据是单个内存间接寻址,因为数据存储在引用的位置。如果没有小的字符串优化,则访问数据将需要两个内存间接调用(首先是加载对字符串的引用并读取其内容,然后是第二个以读取字符串中数据指针的内容)。
正如其他答案所解释的那样,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取决于平台的实现。
std::string
实施”,而另一个人问“ SSO是什么意思”时,您必须绝对疯了,以将他们视为同一个问题