次のテストプログラムを検討してください。
#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倍のメモリを消費します。このメモリの不一致を引き起こす実装はどのように異なりますか?心配する必要がありますか?それを回避するにはどうすればよいですか?
これは、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
も示しています。これは、ヒープから新しい、より大きなバッファを割り当てる前に、文字列が保持できるchar
sの数の尺度です。 gcc-5.0、libc ++、およびVS-2015の実装の場合、これは短い文字列バッファーの尺度です。つまり、短い文字列を保持するためにスタックに割り当てられたサイズバッファにより、より高価なヒープ割り当てを回避できます。
Libc ++実装は、短い文字列の実装の中で最小(スタック使用量)でありながら、最大の短い文字列バッファーを含んでいるようです。そして、totalメモリ使用量(スタック+ヒープ)を数えると、libc ++は、これら4つの実装すべての中でこの2文字の文字列の合計メモリ使用量が最小になります。
これらの測定はすべて64ビットプラットフォームで行われたことに注意してください。 32ビットでは、libc ++スタックの使用量は12になり、小さい文字列バッファーは10になります。32ビットプラットフォームでの他の実装の動作はわかりませんが、上記のコードを使用して調べることができます。 。
あなたは心配する必要はありません、標準ライブラリの実装者は彼らが何をしているのか知っています。
GCCSubversionトランクlibstdc ++からの最新のコードを使用すると、次の数値が得られます。
32 320 344
これは、数週間前に、テストしたコピーオンライトの実装ではなく、デフォルトのstd::string
実装を、小さい文字列の最適化(15文字のスペース)を使用するように切り替えたためです。
概要:_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)
は、全体的なメモリ使用量(スタック+ヒープ)ではなく、スタック使用量のみを表示します。
ソースコードの実際の実装を確認していませんが、C++文字列ライブラリで作業していたときに確認したことを覚えています。 24バイトの文字列の実装が一般的です。文字列の長さが16バイト以下の場合、ヒープからmallocする代わりに、文字列をサイズ16バイトの内部バッファにコピーします。それ以外の場合は、メモリアドレスなどをmallocして格納します。このマイナーなバッファリングは、実行時のパフォーマンスの観点から実際に役立ちます。
一部のコンパイラでは、内部バッファをオフにするオプションがあります。