STLの使用が最適ではない理由について少し

この短い記事では、STLライブラリを使用してアプリケーションのパフォーマンスを簡単かつ単純に無効にする方法について説明します。 このトピック内のライブラリ全体をカバーすることはできないため、1つのコンポーネント(std :: string container)のみが考慮されます。 例として、std :: stringの初期化価格が表示され、その結果、ライブラリの最適でない使用がもたらす可能性が示されます。 以下のすべては、gamedevの分野で特に深刻です。





そのため、例として、文字列のハッシュ値を計算するプリミティブ関数を取りましょう。 2つの実装を提供します-1つはcスタイル、2つ目はstd :: stringを使用したstlスタイルです。 テストケースのソースコードへのリンク



uint32 hash_c( const char * str) { uint32 h = 0; const int len = strlen(str); for ( int i = 0; i < len; ++i) h = 31*h + str[i]; return h; } uint32 hash_stl( const std:: string & str) { uint32 h = 0; for (std:: string ::const_iterator it = str.begin(); it != str.end(); ++it) h = 31*h + + *it; return h; } * This source code was highlighted with Source Code Highlighter .



  1. uint32 hash_c( const char * str) { uint32 h = 0; const int len = strlen(str); for ( int i = 0; i < len; ++i) h = 31*h + str[i]; return h; } uint32 hash_stl( const std:: string & str) { uint32 h = 0; for (std:: string ::const_iterator it = str.begin(); it != str.end(); ++it) h = 31*h + + *it; return h; } * This source code was highlighted with Source Code Highlighter .



  2. uint32 hash_c( const char * str) { uint32 h = 0; const int len = strlen(str); for ( int i = 0; i < len; ++i) h = 31*h + str[i]; return h; } uint32 hash_stl( const std:: string & str) { uint32 h = 0; for (std:: string ::const_iterator it = str.begin(); it != str.end(); ++it) h = 31*h + + *it; return h; } * This source code was highlighted with Source Code Highlighter .



  3. uint32 hash_c( const char * str) { uint32 h = 0; const int len = strlen(str); for ( int i = 0; i < len; ++i) h = 31*h + str[i]; return h; } uint32 hash_stl( const std:: string & str) { uint32 h = 0; for (std:: string ::const_iterator it = str.begin(); it != str.end(); ++it) h = 31*h + + *it; return h; } * This source code was highlighted with Source Code Highlighter .



  4. uint32 hash_c( const char * str) { uint32 h = 0; const int len = strlen(str); for ( int i = 0; i < len; ++i) h = 31*h + str[i]; return h; } uint32 hash_stl( const std:: string & str) { uint32 h = 0; for (std:: string ::const_iterator it = str.begin(); it != str.end(); ++it) h = 31*h + + *it; return h; } * This source code was highlighted with Source Code Highlighter .



  5. uint32 hash_c( const char * str) { uint32 h = 0; const int len = strlen(str); for ( int i = 0; i < len; ++i) h = 31*h + str[i]; return h; } uint32 hash_stl( const std:: string & str) { uint32 h = 0; for (std:: string ::const_iterator it = str.begin(); it != str.end(); ++it) h = 31*h + + *it; return h; } * This source code was highlighted with Source Code Highlighter .



  6. uint32 hash_c( const char * str) { uint32 h = 0; const int len = strlen(str); for ( int i = 0; i < len; ++i) h = 31*h + str[i]; return h; } uint32 hash_stl( const std:: string & str) { uint32 h = 0; for (std:: string ::const_iterator it = str.begin(); it != str.end(); ++it) h = 31*h + + *it; return h; } * This source code was highlighted with Source Code Highlighter .



  7. uint32 hash_c( const char * str) { uint32 h = 0; const int len = strlen(str); for ( int i = 0; i < len; ++i) h = 31*h + str[i]; return h; } uint32 hash_stl( const std:: string & str) { uint32 h = 0; for (std:: string ::const_iterator it = str.begin(); it != str.end(); ++it) h = 31*h + + *it; return h; } * This source code was highlighted with Source Code Highlighter .



  8. uint32 hash_c( const char * str) { uint32 h = 0; const int len = strlen(str); for ( int i = 0; i < len; ++i) h = 31*h + str[i]; return h; } uint32 hash_stl( const std:: string & str) { uint32 h = 0; for (std:: string ::const_iterator it = str.begin(); it != str.end(); ++it) h = 31*h + + *it; return h; } * This source code was highlighted with Source Code Highlighter .



  9. uint32 hash_c( const char * str) { uint32 h = 0; const int len = strlen(str); for ( int i = 0; i < len; ++i) h = 31*h + str[i]; return h; } uint32 hash_stl( const std:: string & str) { uint32 h = 0; for (std:: string ::const_iterator it = str.begin(); it != str.end(); ++it) h = 31*h + + *it; return h; } * This source code was highlighted with Source Code Highlighter .



  10. uint32 hash_c( const char * str) { uint32 h = 0; const int len = strlen(str); for ( int i = 0; i < len; ++i) h = 31*h + str[i]; return h; } uint32 hash_stl( const std:: string & str) { uint32 h = 0; for (std:: string ::const_iterator it = str.begin(); it != str.end(); ++it) h = 31*h + + *it; return h; } * This source code was highlighted with Source Code Highlighter .



  11. uint32 hash_c( const char * str) { uint32 h = 0; const int len = strlen(str); for ( int i = 0; i < len; ++i) h = 31*h + str[i]; return h; } uint32 hash_stl( const std:: string & str) { uint32 h = 0; for (std:: string ::const_iterator it = str.begin(); it != str.end(); ++it) h = 31*h + + *it; return h; } * This source code was highlighted with Source Code Highlighter .



  12. uint32 hash_c( const char * str) { uint32 h = 0; const int len = strlen(str); for ( int i = 0; i < len; ++i) h = 31*h + str[i]; return h; } uint32 hash_stl( const std:: string & str) { uint32 h = 0; for (std:: string ::const_iterator it = str.begin(); it != str.end(); ++it) h = 31*h + + *it; return h; } * This source code was highlighted with Source Code Highlighter .



  13. uint32 hash_c( const char * str) { uint32 h = 0; const int len = strlen(str); for ( int i = 0; i < len; ++i) h = 31*h + str[i]; return h; } uint32 hash_stl( const std:: string & str) { uint32 h = 0; for (std:: string ::const_iterator it = str.begin(); it != str.end(); ++it) h = 31*h + + *it; return h; } * This source code was highlighted with Source Code Highlighter .



uint32 hash_c( const char * str) { uint32 h = 0; const int len = strlen(str); for ( int i = 0; i < len; ++i) h = 31*h + str[i]; return h; } uint32 hash_stl( const std:: string & str) { uint32 h = 0; for (std:: string ::const_iterator it = str.begin(); it != str.end(); ++it) h = 31*h + + *it; return h; } * This source code was highlighted with Source Code Highlighter .







関数が実際に文字列(const char *)を渡すことに注意してください。したがって、2番目の場合、一時オブジェクトstd :: stringが作成されます。 繰り返しますが、テストの本質は、STL文字列の初期化価格を表示することです。



次に、関数の速度を測定します。テスト結果の信頼性のために、256 * 1024パスから計算のサイクルを作成します。 各機能のパフォーマンスは、8、16、32バイトの異なる行サイズで測定されます(これについては、以下で詳しく説明します)。







  1. #define ITERATION_COUNT 256 * 1024
  2. // ...
  3. forint i = 0; i <ITERATION_COUNT; ++ i)
  4. h1 = hash_c(str);
  5. // ...
  6. forint i = 0; i <ITERATION_COUNT; ++ i)
  7. h2 = hash_stl(str);
*このソースコードは、 ソースコードハイライターで強調表示されました。




注:この例は、2010 studioのコンパイラーによってコマンドラインからコンパイルされたもので、コンパイラーキーが次に示されています。







  1. cl / EHsc / O2 main.cpp
  2. ***************************************
  3. 文字列の長さ:8バイト
  4. const char *:6.20ミリ秒、ハッシュ:312017024
  5. std :: string :12.62ミリ秒、ハッシュ:312017024、2.04x
  6. 合計割り当て:0
  7. ***************************************
  8. 文字列の長さ:16バイト
  9. const char *:11.78ミリ秒、ハッシュ:2657714432
  10. std :: string :131.21ミリ秒、ハッシュ:2657714432、11.14x
  11. 合計割り当て:262144
  12. ***************************************
  13. 文字列の長さ:32バイト
  14. const char *:23.20ミリ秒、ハッシュ:3820028416
  15. std :: string :144.64ミリ秒、ハッシュ:3820028416、6.24x
  16. 合計割り当て:262144
*このソースコードは、 ソースコードハイライターで強調表示されました。




ここから楽しみが始まります。 テスト結果は、パフォーマンスの最小の低下が2倍、最大-11倍であることを示しています。 同時に、16バイトを超えるサイズの行の場合、追加の割り当てがヒープに表示されます。 32バイトラインでは、メモリ割り当てコストは計算の背景に対してわずかに平準化され、ドローダウンはわずかに少なくなります-16バイトラインの場合のほぼ2倍です。 割り当てはどこから来ますか?



std :: stringには、文字列を格納するための独自の内部バッファがあり、そのサイズはSTLの実装に依存します。 たとえば、MS Visual Studio 2010のSTL実装では、このバッファーのサイズは16バイトです。これは、ライブラリヘッダーファイル(変数_BUF_SIZE)を調べることで確認できます。 std :: stringが初期化されると、文字列のサイズがチェックされ、内部バッファのサイズよりも小さい場合、文字列はこのバッファに格納されます。それ以外の場合、必要なサイズのメモリがヒープに割り当てられ、文字列は既にそこにコピーされます ご覧のとおり、std :: stringが初期化されるたびに、少なくともデータがコピーされ、文字列サイズがstd :: string内部バッファーのサイズを超える場合の追加の割り当てがコピーされます。 そのため、メモリの断片化につながる割り当てがあるコンパートメントで、リリースアセンブリのパフォーマンスが最大11倍低下するのを観察できます。 最後の瞬間は、コンソールの世界で深刻な問題です。RAMの量は厳密に固定され、仮想メモリはありません。



さて、おそらく、テスト結果をデバッグアセンブリに渡す価値があります。







  1. cl / EHsc / MDd / RTC1 / ZI main.cpp
  2. ***************************************
  3. 文字列の長さ:8バイト
  4. const char *:24.74ミリ秒、ハッシュ:312017024
  5. std :: string :4260.18ミリ秒、ハッシュ:312017024、172.23x
  6. 合計割り当て:262144
  7. ***************************************
  8. 文字列の長さ:16バイト
  9. const char *:34.87ミリ秒、ハッシュ:2657714432
  10. std :: string :7697.69ミリ秒、ハッシュ:2657714432、220.76x
  11. 合計割り当て:524288
  12. ***************************************
  13. 文字列の長さ:32バイト
  14. const char *:58.38ミリ秒、ハッシュ:3820028416
  15. std :: string :14169.49ミリ秒、ハッシュ:3820028416、242.70x
  16. 合計割り当て:524288
*このソースコードは、 ソースコードハイライターで強調表示されました。




いいね! ドローダウンパフォーマンスは240倍になります! 予想されることです。 もちろん、重大な状況では、デバッグ情報フォ​​ーマットc / ZI(編集および続行)を高速/ Ziに変更し、スタック障害および初期化されていない変数(/ RTC1)のさまざまなチェックをオフにして、パフォーマンスを向上させることができますが、この場合、アセンブリデバッグの全体的な意味は消えます。



道徳。 STLは間違いなく便利で強力なツールですが、どのように配置されているのかを把握し、そこから悲しい結果につながらない場所を慎重に選択する必要があります。 そして、これはstd :: stringだけでなく、他のコンポーネントにも適用されます。 したがって、STLを使用する前に、ヒープへの割り当て、メモリの断片化、およびデータの局所性について再検討する必要があります。



最適化に頑張ってください!



All Articles