2010-2-4[木] sprintf,string,stream のメモ.去年だか一昨年ぐらいから、もうそこそこパワーあるのだから、と、 ターゲット環境でc++で string 系を使ってたのだけど、 そのとき不精して、デバッグログでも使い始めたら やたら処理落ちする箇所の原因になってしまったことがある. ログといっても シリアル とか USB とか LAN とか経由しない、 数十KBのメモリに溜め込むだけの処理だったんで、 開発中は release コンパイルでも有効にしていて、 多少重くなるといってもI/Oからまないから大事ないと思って 見過ごしてしまっていたのだった. たしかに想定してた使用量を超えたルートができていたけれど、 それ以上に酷い重さに化けていた. もちろんログをとってることが問題ではなくて、 string系の文字列をつかって記述で楽して
dbgmsg = strFmt("%s (%d) :",fname,line) + msg + '(' + a + ")\n"; のような感じに + 等使って、テンポラリ変数発生、メモリーアロケート発生、 が山ほど起きていたのが敗因. (↑は今適当に書いたので実際のじゃない) 文字列専用にアロケータ用意してフラグメンテーション対策はほどこして いたけど、当然速度的にはよろしいもんじゃなく. わかってみればあたり前だけど、思い込んだら、でなかなか気づけなかった. そのときは、デバッグログからstring系一層してsprintf系で やりすごしたのだけど、 たとえば + でなく += を使えばテンポラリ無くなり アロケート頻度は落ちるし、 キャパシティをある程度コントロールしてアロケート発生させなければ、 書式なしの文字列連結ならsprintfより速くなる 可能性だってあるはず. 文字列連結を一個一個指定することになって記述が面倒だけど ... iostream系の << なんかはやってること的には += と似たようなものの はずなんだよね. などと思うと、stream関係って、実は速度的に 悪いもんじゃないかも、って気がしてきたのだった. て、ことで、例のごとく、ちょっと計測してみた.
test1:
sprintf(buf, "%s(%d) : test> %s", fname, line, msg);
sprintf だとこんな感じになる処理を、ポインタ操作+mem系, str系, std::string, stringstream, strstream, カスタムstream 等で書いて計測.
ということで、phenom2 3GHz環境で、vc9 と mingw g++4.4.0 での結果.
[A][B] は、test_sprintfに与える引数の文字列長の違い. 文字列長が違うと、差のありようもかわるので. (一例の結果だけみて、何倍の差があるとか早とちりしないように:)
やっぱり、ポインタ活用や、strcatを用いたものはそこそこ早く.
アロケートの(ほぼ?)発生しない stringの '+=' やstream系の<<も
まあ早く.
stringstream も、多少気になるかもだが(毎度ローカル変数生成のような)下手な 書き方さえしなければ、悪い速度じゃなさそう. (VCの stream 実装がいまいちぽいが) というか、そもそも printf 系の処理はそんな速いものでもなく...
test2 : 浮動小数点数を使ってみる
sprintf(buf, "%s(%d) : test (%7.5f,%7.5f)%c %s" , fname, line, d1, d1, ch1, msg);
s_stringstream << fname << '(' << line << ") : test (" << d1 << ',' << d1 << ')' << ch1 << ' ' << msg;
double を使ってみた例.
↑で%7.5fにしてるのはstreamでのデフォルトに合わせるため. ソースはこれ で結果( 100000回の平均. 単位:μ秒 )
vc,mingwとも stringstreamがsprintfの倍くらいにばけてる.
test1の結果からすると、必要以上にオーバーヘッドがある気分.
で、STLport版、sprintfと大差なく... なんか速い. 細かいことはあとにして、とりあえず、次.
test3 : 幅指定等
sprintf(buf, "%-24s(%10d) : test %#016llx\n", fname, line, val);
s_stringstream << setw(24) << left << fname << right << '(' << setw(10) << line << ") : test " << setfill('0') << setw(16) << hex << showbase << val << '\n';
真打:-)
ソースはこれ で結果(100000回の平均. 単位:μ秒).
stream、記述のゴツさに、つい比例してしまいそうな印象をもってしまうが、 書式解析がコンパイル時の型チェックでまかなわれているため、 sprintfより速くなる、可能性がある ... vc標準は無視、したいなぁ:)
stringstreamは STLport が がんばっている、って、ことだろうけど.
test1改 : アロケート頻度.test1 において、お手軽な範囲で、無理やり、include関係での malloc や new を乗っ取って、使用数を計測してみた. (ソースこれ追加) ライブラリ.lib 部分になっているのは手がだせてないので、不正確すぎて、判断するには危険だが、大雑把な目安にはなるかも、で. といっても、ヘッダのみなんで、vcは値でたけど、mingwはひっかからず. 以下vcでの1回表示のときの、表示部分でのmalloc:freeの呼び出し数.
vc の string 処理は 16バイトまでならstring内にバッファがあるため、 文字列長でアロケート回数が変わる. stringstream の[A](初回)の値が多かったりfree数が合わないのは、 ライブラリ側の処理とか、あと、バッファ以外にも、 stream処理のサブクラス等の初回生成が含まれているためだろうか. 処理順の都合、stringstreamでアロケートされているけど、 CharAryStream, stringstreamの順に計測すると、CharAryStream側の 回数増えたりするので、可能なものは共通化されてるよう.
で、stringと CharAryStream のstatic変数版が 0回になっているので
アロケートを極力回避しようと思えばできる、って感じ.
stringstream は、static版でも アロケートが発生しているので、 vc版の測定結果が遅い原因だろう. 逆にいえば、g++や STLportのものは、メモリーアロケート発生させてない、 ということでよいか.
test1 追加 : WTL::CString, STLportのstd::string, の追試やっぱり STLportのstd::stringが気になったので、test1の追試. ついでに WTL::CString の結果も. (test1書き直し面倒なんで... vc9,mingwの測定値が若干違うのも面倒で) STLportは v5.2.1 (staticリンク), WTLは8.1のもの.
STLport std::string の '+' での結果がこれって、何だ? って感じだ. STLportはメモリー確保関係工夫してるようなことどっかに書いてあったと思うけど、 いいなあ.
static 変数版を試してないのは実装的にメリットないからだけど、
かわりに Format メンバーを使ったものを併記.
結、というか、雑感たったこれだけのお試しで結論するのも不味いという気もなくはないし、 使い慣れてない iostream系は何かぽかってないか不安もあるけど、 とりあえず.
iostream系は、それだけなら速度パフォーマンスのそう悪い仕組みでない、といったところか.
でも、実装は工夫が必要なんだろう. vcやg++のものが工夫が足りてない、というより、 STLport ががんばっているんだろうし. stream よりも string のほうが速度ペナルティが多くなりそうだ. += じゃなくて + で楽したいんだし. 少なくとも string 使ってる奴が速度パフォーマンスを理由に stream系にケチをつけちゃいけないだろう(自戒:) ただ stringstream は 無様に思えてきた.
strstream は完全に捨て去る必要ないよなあ、で.
vc標準の奴の性能があれなんで躊躇しちゃうけど、
速度を気にするならstrstream(かその代用品)使うのも手かも、と.
まあ速くないといっても、この程度の差で困ることは今時まず無いので 気にしないか. (printf系の使用を避けて速度アップを実感できたのは8bit,16bit機時代くらいか).
今回の結果はまさに50歩100歩といったところだし.
デバッグ処理で極力メモリーアロケートなんて起きて欲しくないわけだし. もちろん、printf系だって全くメモリーアロケートしないかというと 実装しだいだし書式によってはありえるのだけれど. ただ通常極力しないように作られてるし、 デバッグで通常出す程度ならまず起きないし、で.
今回のソース関係のzip : [download]
|