1. ホーム
  2. c++

[解決済み] libc++の短い文字列の最適化の仕組みとは?

2022-09-16 22:19:46

質問

この回答 は、短い文字列の最適化 (SSO) の素晴らしいハイレベルな概要を提供しています。しかし、私はそれが実際にどのように動作するのか、特に libc++ の実装においてより詳細に知りたいと思います。

  • SSO のために修飾するために、文字列はどのくらい短くなければならないのでしょうか。 これはターゲットとなるアーキテクチャに依存するのでしょうか。

  • 文字列データにアクセスする際、実装はどのように短い文字列と長い文字列を区別しているのでしょうか? を区別しているのでしょうか?単純に m_size <= 16 なのか、それとも他のメンバ変数の一部であるフラグなのでしょうか?(I 想像するに m_size またはその一部もまた 文字列データを格納するために使用されるかもしれません)。

libc++ が SSO を使用することを知っているので、私はこの質問を特に libc++ に対して行いました。 libc++ ホームページ .

以下は ソース :

libc++ は文字列クラスに対して、2 つの微妙に異なるメモリレイアウトでコンパイルすることができます。 _LIBCPP_ALTERNATE_STRING_LAYOUT フラグによって決定されます。どちらのレイアウトもリトルエンディアンとビッグエンディアンのマシンを区別しており、合計で 4 種類の異なるレイアウトがあります。以下では、quot;normal" レイアウトとリトルエンディアンを仮定します。

さらに次のように仮定します。 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_stringsizeof 3語で、すべてのアーキテクチャで sizeof(word) == sizeof(void*) . long/short フラグ、および short フォームのサイズ フィールドを正しく分解しています。

短い文字列の容量である __min_cap は、異なるアーキテクチャに対してどのような値を取るのでしょうか?

短い形式では、扱う単語は3つです。

  • 1ビットはロング/ショートのフラグになります。
  • 7ビットがサイズになります。
  • 仮定すると char と仮定すると、1 バイトが末尾の null になります(libc++ は常にデータの後ろに末尾の null を格納します)。

これは、短い文字列を格納するために3ワードから2バイトを引いたものです(つまり、最大の capacity() アロケーションなし)。

32ビットマシンでは、短い文字列に10文字が収まります。 sizeof(string)は12です。

64ビットマシンでは、22文字が短い文字列に収まります。

主要な設計目標は 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であり、誤ってlibc++と混同すると std::string の異なる設定でコンパイルされた _LIBCPP_ABI_ALTERNATE_STRING_LAYOUT はランタイムエラーを発生させます。

このフラグは、libc++のベンダによってのみ変更されることをお勧めします。