この回答 は、短い文字列の最適化(SSO)の概要を示しています。ただし、実際の動作、特にlibc ++実装での動作について詳しく知りたいと思います。
SSOの資格を得るために、文字列はどれくらい短い必要がありますか?これはターゲットアーキテクチャに依存しますか?
文字列データにアクセスするときに、実装はどのように短い文字列と長い文字列を区別しますか? m_size <= 16
のように単純ですか、それとも他のメンバー変数の一部であるフラグですか? (m_size
またはその一部は、文字列データの保存にも使用されると思います)。
SSOを使用していることを知っているため、libc ++専用にこの質問をしました。これは libc ++ホームページ にも記載されています。
ソース を見た後の観察結果を次に示します。
libc ++は、文字列クラスの2つのわずかに異なるメモリレイアウトでコンパイルできます。これは_LIBCPP_ALTERNATE_STRING_LAYOUT
フラグによって管理されます。どちらのレイアウトもリトルエンディアンとビッグエンディアンのマシンを区別しているため、合計4つの異なるバリアントがあります。以下では、「通常の」レイアウトとリトルエンディアンを想定します。
さらにsize_type
が4バイトであり、value_type
が1バイトであると仮定すると、これは文字列の最初の4バイトがメモリ上でどのように見えるかです。
// short string: (s)ize and 3 bytes of char (d)ata
sssssss0;dddddddd;dddddddd;dddddddd
^- is_long = 0
// long string: (c)apacity
ccccccc1;cccccccc;cccccccc;cccccccc
^- is_long = 1
短い文字列のサイズは上位7ビットであるため、それにアクセスするときにシフトする必要があります。
size_type __get_short_size() const {
return __r_.first().__s.__size_ >> 1;
}
同様に、長い文字列の容量のゲッターとセッターは、__long_mask
を使用してis_long
ビットを回避します。
私はまだ最初の質問への答えを探しています、すなわち、__min_cap
、短い文字列の容量は、異なるアーキテクチャにどのような値をとるでしょうか?
他の標準ライブラリの実装
この回答 は、他の標準ライブラリ実装におけるstd::string
メモリレイアウトの概要を示します。
Libc ++ _basic_string
_は、すべてのアーキテクチャでsizeof
3ワードを持つように設計されています(sizeof(Word) == sizeof(void*)
)。ロングフラグとショートフラグ、および短い形式のサイズフィールドを正しく分析しました。
短い文字列の容量である__min_capは、異なるアーキテクチャに対してどのような値を取りますか?
短い形式では、作業する3つの単語があります。
char
を想定すると、1バイトが末尾のヌルに移動します(libc ++は常にデータの後ろに末尾のヌルを格納します)。これにより、短い文字列(つまり、割り当てなしの最大のcapacity()
)を格納するために、3ワードから2バイトを引いたものが残ります。
32ビットマシンでは、10文字が短い文字列に収まります。 sizeof(string)は12です。
64ビットマシンでは、22文字が短い文字列に収まります。 sizeof(string)は24です。
主要な設計目標は、sizeof(string)
を最小限に抑えながら、内部バッファをできるだけ大きくすることでした。その理由は、移動の構築と移動の割り当てを高速化することです。 sizeof
が大きいほど、移動構築または移動割り当て中に移動する必要がある単語が増えます。
長い形式では、データポインター、サイズ、および容量を格納するために最低3ワードが必要です。したがって、短い形式を同じ3つの単語に制限しました。 4ワードのsizeofの方がパフォーマンスが向上する可能性が示唆されています。私はその設計選択をテストしていません。
_ LIBCPP_ABI_ALTERNATE_STRING_LAYOUT
「長いレイアウト」が次のように変更されるようにデータメンバーを再配置する__LIBCPP_ABI_ALTERNATE_STRING_LAYOUT
_という構成フラグがあります。
_struct __long
{
size_type __cap_;
size_type __size_;
pointer __data_;
};
_
に:
_struct __long
{
pointer __data_;
size_type __size_;
size_type __cap_;
};
_
この変更の動機は、___data_
_を最初に配置すると、アライメントが改善されるため、パフォーマンス上の利点がいくつかあるという信念です。パフォーマンスの利点を測定しようとしましたが、測定が困難でした。パフォーマンスが低下することはなく、わずかに改善される場合があります。
フラグは注意して使用する必要があります。これは別のABIであり、誤って_std::string
_の異なる設定でコンパイルされたlibc ++ __LIBCPP_ABI_ALTERNATE_STRING_LAYOUT
_と誤って混在すると、ランタイムエラーが発生します。
このフラグは、libc ++のベンダーのみが変更することをお勧めします。
libc ++実装 は少し複雑です。代替設計を無視し、リトルエンディアンのコンピューターを想定します。
_template <...>
class basic_string {
/* many many things */
struct __long
{
size_type __cap_;
size_type __size_;
pointer __data_;
};
enum {__short_mask = 0x01};
enum {__long_mask = 0x1ul};
enum {__min_cap = (sizeof(__long) - 1)/sizeof(value_type) > 2 ?
(sizeof(__long) - 1)/sizeof(value_type) : 2};
struct __short
{
union
{
unsigned char __size_;
value_type __lx;
};
value_type __data_[__min_cap];
};
union __ulx{__long __lx; __short __lxx;};
enum {__n_words = sizeof(__ulx) / sizeof(size_type)};
struct __raw
{
size_type __words[__n_words];
};
struct __rep
{
union
{
__long __l;
__short __s;
__raw __r;
};
};
__compressed_pair<__rep, allocator_type> __r_;
}; // basic_string
_
注:___compressed_pair
_は、本質的に Empty Base Optimization 、別名_template <T1, T2> struct __compressed_pair: T1, T2 {};
_;向けに最適化されたペアです。すべての意図と目的のために、あなたはそれを通常のペアと考えることができます。 _std::allocator
_はステートレスであるため空であるため、その重要性が明らかになります。
さて、これはかなり生なので、メカニズムをチェックしましょう!内部的に、多くの関数は__get_pointer()
を呼び出し、それ自体が___is_long
_を呼び出して、文字列が___long
_または___short
_表現を使用しているかどうかを判断します。
_bool __is_long() const _NOEXCEPT
{ return bool(__r_.first().__s.__size_ & __short_mask); }
// __r_.first() -> __rep const&
// .__s -> __short const&
// .__size_ -> unsigned char
_
正直に言うと、これが標準C++であるかどうかはあまりわかりません(union
の最初のサブシーケンスプロビジョニングは知っていますが、匿名のユニオンとエイリアシングが一緒にスローされる方法はわかりません)が、標準ライブラリは許可されますとにかく実装定義の動作を利用する。