std :: stringの文脈での頭字語SSOの意味


155

では、最適化とコードのスタイルについてC ++質問、いくつかの答えは、のコピーを最適化する文脈で「SSO」を参照しましたstd::string。そのコンテキストでSSOはどういう意味ですか?

明らかに「シングルサインオン」ではありません。「共有文字列の最適化」でしょうか。


57
これは、「2 + 2とは」が「200/50の結果とは」の複製であるのと同じ方法での複製にすぎません。答えは同じです。問題は完全に異なります。「重複として閉じる」は、複数の人が同じ*の質問をするときに使用することを目的としています。ある人が「std::string実装方法」を尋ね、別の人が「SSOの意味」を尋ねた場合、それらを同じ質問であると考えるのはまったく正気でない必要があります
jalf

1
@jalf:この質問の範囲を正確に網羅する既存のQ + Aがある場合は、それを重複と見なします(OPがこれを自分で検索する必要があると言っているのではありません。すでにカバーされています。)
Oliver Charlesworth 2012

47
あなたは効果的にOPに「あなたの質問は間違っています。しかし、何を尋ねるべきかを知るためには答えを知る必要がありました」と言っています。SOをオフにする良い方法です。また、必要な情報を見つけるのがむずかしくなります。人々が質問をしなかった場合(そして、終了で「この質問はすべきではなかった」と効果的に言っている場合)、その答えをまだ知らない人がこの質問に対する答えを得る方法はありません
ジャルフ

7
@jalf:全然。IMO、「閉じる投票」は「悪い質問」を意味するものではありません。私はそのために反対票を使います。答えが「未定義の振る舞い」である無数の質問(i = i ++など)はすべて互いに重複しているという意味で、重複と見なします。別の言い方をすれば、重複していない質問に誰も答えなかったのはなぜですか?
Oliver Charlesworth 2012

5
@jalf:私はオリに同意します。質問は重複ではありませんが、答えはそうなります。そのため、すでにある答えが適切である別の質問にリダイレクトします。重複は消えないので、閉じられた質問は、代わりに、答えが存在する別の質問へのポインターとして機能します。次にSSOを探している人はここに行き、リダイレクトをたどり、彼女の答えを見つけます。
Matthieu M.12年

回答:


212

背景/概要

自動変数(malloc/ を呼び出さずに作成する変数である「スタックから」new)の操作は、フリーストア(「ヒープ」、つまりを使用して作成される変数)を伴う操作よりもはるかに高速ですnew。ただし、自動配列のサイズはコンパイル時に固定されますが、フリーストアの配列のサイズは固定されていません。さらに、スタックサイズは制限されていますが(通常は数MiB)、空きストアはシステムのメモリによってのみ制限されます。

SSOは、短い/小さい文字列の最適化です。std::string通常、あなたがコールした場合と同様の性能特性を提供する無料のストア(「ヒープ」)へのポインタとして文字列を格納しますnew char [size]。これにより、非常に大きな文字列のスタックオーバーフローが防止されますが、特にコピー操作で遅くなる可能性があります。最適化として、の多くの実装はstd::string、のような小さな自動配列を作成しますchar [20]。20文字以下の文字列がある場合(この例では、実際のサイズは異なります)、その配列に直接格納されます。これにより、呼び出す必要がnewまったくなくなり、処理が少し高速になります。

編集:

私はこの回答がそれほど人気が​​あるとは予想していませんでしたが、人気があるので、実際のSSOの実装を実際に読んだことがないという警告とともに、より現実的な実装を紹介します。

実装の詳細

少なくとも、std::stringは次の情報を保存する必要があります。

  • サイズ
  • 容量
  • データの場所

サイズは、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ビットシステムの場合、これは通常、std::string文字列あたり24バイトの「オーバーヘッド」に加えて、SSOバッファー用にさらに16バイト(パディング要件により、ここでは20ではなく16を選択)であることを意味します。私の簡略化した例のように、これら3つのデータメンバーと文字のローカル配列を格納することは実際には意味がありません。もしそうなら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

ほとんどの開発者がconst参照を使用してstd :: stringを渡す場合、SSOは本当に実用的ですか?
グプタ

1
SSOには、コピーを安価にすること以外に2つの利点があります。1つ目は、文字列サイズが小さなバッファーサイズに収まる場合、初期構築に割り当てる必要がないことです。2つ目は、関数がを受け入れる場合std::string const &、データは参照の場所に格納されるため、データを取得することは単一メモリの間接参照であることです。小さな文字列の最適化がなかった場合、データへのアクセスには2つのメモリの間接化が必要になります(最初に文字列への参照をロードしてその内容を読み取るため、次に文字列内のデータポインターの内容を読み取るため)。
デビッドストーン

34

SSOは "Small String Optimization"の略で、個別に割り当てられたバッファを使用するのではなく、文字列クラスの本体に小さな文字列を埋め込む手法です。


15

すでに他の回答で説明されているように、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がプラットフォームの実装に依存することが確認されます。

Ubuntu UbuntuでのSSOベンチマーク

Macbook Pro Macbook ProでのSSOベンチマーク

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.