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

C++ std::string上下文中首字母缩略词SSO的含义,c++,string,optimization,C++,String,Optimization,在中,有几个答案在优化std::string副本的上下文中提到了“SSO”。在这种情况下,SSO意味着什么 显然不是“单一登录”。“共享字符串优化”,也许?SSO是“小字符串优化”的缩写,这是一种将小字符串嵌入字符串类主体而不是使用单独分配的缓冲区的技术。背景/概述 对自动变量(“从堆栈”的操作,这是在不调用malloc/new的情况下创建的变量)的操作通常比涉及空闲存储(“堆”,这是使用new创建的变量)的操作快得多。但是,自动数组的大小在编译时是固定的,但是来自空闲存储的数组的大小不是固定的

在中,有几个答案在优化
std::string
副本的上下文中提到了“SSO”。在这种情况下,SSO意味着什么


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

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

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

SSO是短/小字符串优化。
std::string
通常将字符串存储为指向空闲存储(“堆”)的指针,这会提供类似于调用
new char[size]
的性能特征。这可以防止非常大的字符串出现堆栈溢出,但速度可能较慢,尤其是在执行复制操作时。作为一种优化,许多std::string的实现创建了一个小型的自动数组,类似于
char[20]
。如果您有一个小于或等于20个字符的字符串(在本例中,实际大小会有所不同),它会将其直接存储在该数组中。这样就完全不需要调用
new
,从而加快了速度

编辑:

我没想到这个答案会如此流行,但既然如此,让我给出一个更现实的实现,但要注意的是,我从来没有在“野外”阅读过任何SSO的实现

实施细节 至少,
std::string
需要存储以下信息:

  • 大小
  • 容量
  • 数据的位置
大小可以存储为
std::string::size\u类型
,也可以存储为指向末尾的指针。唯一的区别是,当用户调用
size
时,您是想减去两个指针,还是想在用户调用
end
时向指针添加
size\u type
。容量也可以以任何一种方式存储

你不用的东西你不用付钱。

首先,考虑基于以上概述的天真的实现:

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

我假设大多数实现看起来更像这样。

正如其他答案所解释的,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;
}
#包括
#包括
#包括
#包括
静态常量字符[]=“0123456789abcdefghijklmnopqrstuvxyzabcdefghijklmnopqrstuvxyz”;
静态常量int数组_SIZE=sizeof(CHARS)-1;
静态常量int BENCHMARK_SIZE=10000000;
静态常量int最大字符串长度=50;
使用时间点=标准::时钟::高分辨率时钟::时间点;
无效基准(标准::向量和列表){
std::chrono::高分辨率时钟::时间点t1=std::chrono::高分辨率时钟::现在();
//强制循环迭代中每个字符串的副本
用于(常数自动:列表){

std::cout这只是一个重复,就像“what is 2+2”是“what is result of 200/50”的重复一样。答案是一样的。问题完全不同。“Close as duplicate”用于当多人问同一*问题时。当一个人问“std::string是如何实现的?”另一个则问“SSO意味着什么”,你必须绝对疯狂地认为它们是一样的。question@jalf如果有一个现有的Q+A完全包含了这个问题的范围,我会认为它是一个复制品。(我并不是说OP应该自己搜索这个问题,只是这里的任何答案都将覆盖已经覆盖的领域。)你实际上是在告诉OP“你的问题是错误的。但是你需要知道答案才能知道你应该问什么”。这是一个很好的让人反感的方法。它也会让你毫无必要地难以找到你需要的信息。如果人们不提问(而结尾实际上是说“这个问题不应该被问到”),那么对于那些还不知道答案的人来说,就不可能找到答案question@jalfIMO,“投票结束”并不意味着“坏问题”。我用下注来投票。我认为这是一个重复的问题,所有的问题(i= i++等等)的答案是“未定义的行为”。是相互重复的。另一方面,如果问题不是重复的,为什么没有人回答这个问题?@jalf:我同意奥利的观点,这个问题不是重复的,但答案会是重复的,因此,将问题重定向到另一个答案已经存在的问题上似乎是合适的。作为重复关闭的问题不会消失,而是会消失充当指向另一个人的指针
#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;
}