web-dev-qa-db-ja.com

libc ++のstd :: stringの実装がlibstdc ++の3倍のメモリを使用するのはなぜですか?

次のテストプログラムを検討してください。

#include <iostream>
#include <string>
#include <vector>

int main()
{
    std::cout << sizeof(std::string("hi")) << " ";
    std::string a[10];
    std::cout << sizeof(a) << " ";
    std::vector<std::string> v(10);
    std::cout << sizeof(v) + sizeof(std::string) * v.capacity() << "\n";
}

それぞれlibstdc++libc++の出力は次のとおりです。

8 80 104
24 240 264

ご覧のとおり、libc++は単純なプログラムの3倍のメモリを消費します。このメモリの不一致を引き起こす実装はどのように異なりますか?心配する必要がありますか?それを回避するにはどうすればよいですか?

16
user4390444

これは、std::stringの両方の種類のメモリ使用量(スタックとヒープ)を調べるのに役立つ短いプログラムです。

#include <string>
#include <new>
#include <cstdio>
#include <cstdlib>

std::size_t allocated = 0;

void* operator new (size_t sz)
{
    void* p = std::malloc(sz);
    allocated += sz;
    return p;
}

void operator delete(void* p) noexcept
{
    return std::free(p);
}

int
main()
{
    allocated = 0;
    std::string s("hi");
    std::printf("stack space = %zu, heap space = %zu, capacity = %zu\n",
     sizeof(s), allocated, s.capacity());
}

http://melpon.org/wandbox/ を使用すると、さまざまなコンパイラ/ライブラリの組み合わせの出力を簡単に取得できます。次に例を示します。

gcc 4.9.1:

stack space = 8, heap space = 27, capacity = 2

gcc 5.0.0:

stack space = 32, heap space = 0, capacity = 15

clang/libc ++:

stack space = 24, heap space = 0, capacity = 22

VS-2015:

stack space = 32, heap space = 0, capacity = 15

(最後の行は http://webcompiler.cloudapp.net からです)

上記の出力は、capacityも示しています。これは、ヒープから新しい、より大きなバッファを割り当てる前に、文字列が保持できるcharsの数の尺度です。 gcc-5.0、libc ++、およびVS-2015の実装の場合、これは短い文字列バッファーの尺度です。つまり、短い文字列を保持するためにスタックに割り当てられたサイズバッファにより、より高価なヒープ割り当てを回避できます。

Libc ++実装は、短い文字列の実装の中で最小(スタック使用量)でありながら、最大の短い文字列バッファーを含んでいるようです。そして、totalメモリ使用量(スタック+ヒープ)を数えると、libc ++は、これら4つの実装すべての中でこの2文字の文字列の合計メモリ使用量が最小になります。

これらの測定はすべて64ビットプラットフォームで行われたことに注意してください。 32ビットでは、libc ++スタックの使用量は12になり、小さい文字列バッファーは10になります。32ビットプラットフォームでの他の実装の動作はわかりませんが、上記のコードを使用して調べることができます。 。

54
Howard Hinnant

あなたは心配する必要はありません、標準ライブラリの実装者は彼らが何をしているのか知っています。

GCCSubversionトランクlibstdc ++からの最新のコードを使用すると、次の数値が得られます。

32 320 344

これは、数週間前に、テストしたコピーオンライトの実装ではなく、デフォルトのstd::string実装を、小さい文字列の最適化(15文字のスペース)を使用するように切り替えたためです。

10
Jonathan Wakely

概要:_libstdc++_が1つの_char*_を使用しているように見えます。実際、より多くのメモリを割り当てます。

したがって、Clangの_libc++_実装がメモリ効率が悪いことを心配する必要はありません。

Libstdc ++のドキュメントから( 詳細な説明 の下):

_A string looks like this:

                                        [_Rep]
                                        _M_length
   [basic_string<char_type>]            _M_capacity
   _M_dataplus                          _M_refcount
   _M_p ---------------->               unnamed array of char_type
_

ここで、_M_pは文字列の最初の文字を指し、それを-_Repへのポインターにキャストし、1を引いてヘッダーへのポインターを取得します。

このアプローチには、文字列オブジェクトに必要な割り当てが1つだけであるという大きな利点があります。すべての醜さは、インライン関数の1つのペアに限定され、それぞれが1つの追加命令にコンパイルされます。_Rep:: _ M_data()およびstring :: _ M_rep();生のバイトのブロックを取得し、十分なスペースを確保して、前面に_Repオブジェクトを作成する割り当て関数。

_M_dataが_Repではなく文字配列を指すようにする理由は、デバッガーが文字列の内容を確認できるようにするためです。 (おそらく、デバッガーが使用する_Repを取得するために非インラインメンバーを追加して、ユーザーが実際の文字列の長さを確認できるようにする必要があります。)

したがって、1つの_char*_のように見えますが、メモリ使用量の点で誤解を招く可能性があります。

以前は、_libstdc++_は基本的に次のレイアウトを使用していました。

_  struct _Rep_base
  {
    size_type               _M_length;
    size_type               _M_capacity;
    _Atomic_Word            _M_refcount;
  };
_

これは、_libc++_の結果に近いものです。

_libc++_は「短い文字列の最適化」を使用します。正確なレイアウトは、__LIBCPP_ABI_ALTERNATE_STRING_LAYOUT_が定義されているかどうかによって異なります。定義されている場合、文字列が短いとデータポインタはワード整列されます。詳しくは ソースコード をご覧ください。

短い文字列の最適化はヒープの割り当てを回避するため、スタックに割り当てられている部分のみを考慮すると、_libstdc++_の実装よりもコストがかかるように見えます。 sizeof(std::string)は、全体的なメモリ使用量(スタック+ヒープ)ではなく、スタック使用量のみを表示します。

7
Philipp Claßen

ソースコードの実際の実装を確認していませんが、C++文字列ライブラリで作業していたときに確認したことを覚えています。 24バイトの文字列の実装が一般的です。文字列の長さが16バイト以下の場合、ヒープからmallocする代わりに、文字列をサイズ16バイトの内部バッファにコピーします。それ以外の場合は、メモリアドレスなどをmallocして格納します。このマイナーなバッファリングは、実行時のパフォーマンスの観点から実際に役立ちます。

一部のコンパイラでは、内部バッファをオフにするオプションがあります。

2
mostruash