C ++標準ライブラリのiostreamのパフォーマンスが遅いことに言及するたびに、信じられないような波に遭遇します。それでも、iostreamライブラリコード(コンパイラの完全な最適化)に費やされた大量の時間を示すプロファイラー結果があり、iostreamからOS固有のI / O APIおよびカスタムバッファー管理に切り替えると、桁違いに改善されます。
C ++標準ライブラリはどのような追加の作業を行っていますか、それは標準で必要とされていますか、それは実際に役立ちますか?あるいは、一部のコンパイラーは、手動バッファー管理と競合するiostreamの実装を提供していますか?
ベンチマーク
問題を解決するために、iostreamの内部バッファリングを実行する短いプログラムをいくつか作成しました。
- バイナリデータを
ostringstream
http://ideone.com/2PPYwに配置する - バイナリデータを
char[]
バッファに入れるhttp://ideone.com/Ni5ct - http://ideone.com/Mj2Fi
vector<char>
を使用してバイナリデータをback_inserter
- NEW:
vector<char>
シンプルなイテレーターhttp://ideone.com/9iitv - NEW:バイナリデータをhttp://ideone.com/qc9QAに直接配置
stringbuf
- NEW:
vector<char>
単純なイテレーターと境界チェックhttp://ideone.com/YyrKy
ostringstream
とstringbuf
バージョンは、実行速度が非常に遅いため、実行される反復が少ないことに注意してください。
ideoneに、ostringstream
よりも約3倍遅いstd:copy
+ back_inserter
+ std::vector
、および約15倍より遅いmemcpy
生バッファへ。これは、実際のアプリケーションをカスタムバッファリングに切り替えたときのプロファイリング前後のプロファイルと一致しています。
これらはすべてメモリ内バッファーであるため、iostreamの速度が遅いディスクI / O、過度のフラッシュ、stdioとの同期、またはC ++標準ライブラリーの観察された速度の遅さを弁解するために使用するその他のものに起因するものではありません。 iostream。
他のシステムのベンチマークや、一般的な実装(gccのlibc ++、Visual C ++、Intel C ++など)が行うこと、および標準によってどの程度のオーバーヘッドが義務付けられているかについての解説を見るとよいでしょう。
このテストの根拠
多くの人々は、フォーマットされた出力にはiostreamがより一般的に使用されていると正しく指摘しています。ただし、これらは、バイナリファイルアクセス用のC ++標準によって提供される唯一の最新のAPIでもあります。しかし、内部バッファリングでパフォーマンステストを実行する本当の理由は、一般的なフォーマット済みI / Oに当てはまります:iostreamがディスクコントローラーにrawデータを供給できない場合、フォーマットを担当しているときに、どのようにして対応できるでしょうか?
ベンチマークのタイミング
これらはすべて、外側の(k
)ループの反復ごとです。
ideone(gcc-4.3.4、不明なOSおよびハードウェア):
ostringstream
:53ミリ秒stringbuf
:27ミリ秒vector<char>
およびback_inserter
:17.6ミリ秒vector<char>
通常のイテレータの場合:10.6 msvector<char>
イテレータと境界チェック:11.4 mschar[]
:3.7ミリ秒
私のラップトップ(Visual C ++ 2010 x86 、、、cl /Ox /EHsc
Windows 7 Ultimate 64ビット、Intel Core i7、8 GB RAM):
ostringstream
:73.4ミリ秒、71.6ミリ秒stringbuf
:21.7 ms、21.3 msvector<char>
およびback_inserter
:34.6 ms、34.4 msvector<char>
通常のイテレータの場合:1.10 ms、1.04 msvector<char>
イテレータと境界チェック:1.11 ms、0.87 ms、1.12 ms、0.89 ms、1.02 ms、1.14 mschar[]
:1.48 ms、1.57 ms
プロファイルに基づく最適化を使用してVisual C ++ 2010のx86、 、cl /Ox /EHsc /GL /c
、link /ltcg:pgi
実行、link /ltcg:pgo
、対策:
ostringstream
:61.2 ms、60.5 msvector<char>
通常のイテレータの場合:1.04 ms、1.03 ms
同じラップトップ、同じOS、cygwin gcc 4.3.4を使用g++ -O3
:
ostringstream
:62.7 ms、60.5 msstringbuf
:44.4 ms、44.5 msvector<char>
およびback_inserter
:13.5 ms、13.6 msvector<char>
通常のイテレータの場合:4.1 ms、3.9 msvector<char>
イテレータと境界チェック:4.0 ms、4.0 mschar[]
:3.57 ms、3.75 ms
同じラップトップ、Visual C ++ 2008 SP1 cl /Ox /EHsc
:
ostringstream
:88.7 ms、87.6 msstringbuf
:23.3 ms、23.4 msvector<char>
およびback_inserter
:26.1 ms、24.5 msvector<char>
通常のイテレータの場合:3.13 ms、2.48 msvector<char>
イテレータと境界チェック:2.97 ms、2.53 mschar[]
:1.52 ms、1.25 ms
同じラップトップ、Visual C ++ 2010 64ビットコンパイラ:
ostringstream
:48.6 ms、45.0 msstringbuf
:16.2 ms、16.0 msvector<char>
およびback_inserter
:26.3 ms、26.5 msvector<char>
通常のイテレータの場合:0.87 ms、0.89 msvector<char>
イテレータと境界チェック:0.99 ms、0.99 mschar[]
:1.25 ms、1.24 ms
編集:2回すべて実行して、結果の一貫性を確認します。かなり一貫したIMO。
注:私のラップトップでは、ideoneが許可するよりも多くのCPU時間を節約できるため、すべてのメソッドの反復回数を1000に設定しました。この手段ostringstream
とvector
だけ最初のパスで行われます再配分は、最終的な結果にはほとんど影響を持っている必要があります。
編集:おっと、vector
-with-ordinary-iteratorにバグが見つかりました。イテレータは高度ではなかったため、キャッシュヒットが多すぎました。私はどのようvector<char>
に優れているか疑問に思っていましたchar[]
。ただし、それほど大きな違いはありませんでしたが、VC ++ 2010 vector<char>
よりも高速ですchar[]
。
結論
出力ストリームのバッファリングには、データが追加されるたびに3つのステップが必要です。
- 入力ブロックが利用可能なバッファスペースに適合することを確認します。
- 入力ブロックをコピーします。
- データ終了ポインタを更新します。
私が投稿した最新のコードスニペット「vector<char>
単純なイテレータと境界チェック」は、これを行うだけでなく、追加のスペースを割り当て、受信ブロックが収まらない場合に既存のデータを移動します。クリフォードが指摘したように、ファイルI / Oクラスでのバッファリングはそれを行う必要はなく、現在のバッファーをフラッシュして再利用するだけです。したがって、これは出力をバッファリングするコストの上限になるはずです。そして、それはメモリ内バッファを機能させるために必要なことです。
では、stringbuf
ideoneでは2.5倍遅く、テストすると少なくとも10倍遅くなるのはなぜですか。この単純なマイクロベンチマークでは多態的に使用されていないため、説明はありません。
std::ostringstream
バッファサイズを指数関数的に増やすほど賢くない場合std::vector
、それは(A)愚かであり、(B)I / Oパフォーマンスについて考える人が考慮すべきことです。とにかく、バッファは再利用され、毎回再割り当てされるわけではありません。またstd::vector
、動的に増加するバッファも使用しています。私はここで公平になるようにしています。
ostringstream
可能な限り高速なパフォーマンスが必要な場合は、に直接進むことを検討してくださいstringbuf
。ostream
クラスは通じ柔軟なバッファの選択(ファイル、文字列など)と一緒に、ロケールを意識書式設定機能を結びつけるために仮定されrdbuf()
、その仮想関数インタフェース。書式設定を行わない場合、その余分なレベルの間接参照は、他のアプローチと比較して比例して高価に見えるでしょう。
ofstream
に移動することでfprintf
、次数または絶対値の速度が向上しました。WinXPsp3上のMSVC 2008。iostreamsはただ遅いだけです。