では、最適化とコードのスタイルについてC ++質問、いくつかの答えは、のコピーを最適化する文脈で「SSO」を参照しましたstd::string
。そのコンテキストでSSOはどういう意味ですか?
明らかに「シングルサインオン」ではありません。「共有文字列の最適化」でしょうか。
では、最適化とコードのスタイルについてC ++質問、いくつかの答えは、のコピーを最適化する文脈で「SSO」を参照しましたstd::string
。そのコンテキストでSSOはどういう意味ですか?
明らかに「シングルサインオン」ではありません。「共有文字列の最適化」でしょうか。
回答:
自動変数(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;
};
};
ほとんどの実装はこれに似ていると思います。
std::string const &
、データは参照の場所に格納されるため、データを取得することは単一メモリの間接参照であることです。小さな文字列の最適化がなかった場合、データへのアクセスには2つのメモリの間接化が必要になります(最初に文字列への参照をロードしてその内容を読み取るため、次に文字列内のデータポインターの内容を読み取るため)。
すでに他の回答で説明されているように、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
実装方法」を尋ね、別の人が「SSOの意味」を尋ねた場合、それらを同じ質問であると考えるのはまったく正気でない必要があります