文字列の追加処理のパフォーマンス

TECSの半分は,raccを用いた構文解析,残りの半分はファイルの生成.つまり,文字列操作がパフォーマンスに与える影響はムチャクチャ大きい.OutOfMemoryError で落ちるのはとりあえず横において,速度パフォーマンスについて考えてみる.
適切なベンチマークを作れるほどrubyのことが判っていないので,人様のコードを借りてきた.

require "benchmark"
Benchmark.bm do |x|
  str = ''; x.report('+=') { 10000.times{ str += "abcdefg" } }
  str = ''; x.report('<<') { 10000.times{ str << "abcdefg" } }
  str = ''; x.report('+') { 10000.times{ str = str + "abcdefg" } }
  str = ''; x.report('#{}') { 10000.times{ str = "#{str}abcdefg" } }
end

jruby-1.3.0 + JavaVM1.6.0 (64bits) on OSX10.5.6 での結果は,こんな感じ.

      user     system      total        real
+=  0.391000   0.000000   0.391000 (  0.336000)
<<  0.070000   0.000000   0.070000 (  0.070000)
+  0.230000   0.000000   0.230000 (  0.230000)
#{}  0.296000   0.000000   0.296000 (  0.296000)

トレンドが判れば十分なので,誤差は測らない.何回か繰り返した感じだと10%程度の誤差は出る感じ.
systemが0というのは…どう理解すればよいのか判らない.演算子毎の比較をするには,判らないままでも十分だろうから深く考えない.


比較対象として,OSX純正のCRuby (ruby 1.8.6 (2008-08-11 patchlevel 287) [universal-darwin9.0])を引っ張り出してみた.
処理系ごとに独自の最適化をしているというのは大いに有り得るので,jrubyの結果だけ鵜呑みにすると痛い目を見そうだから.

      user     system      total        real
+=  0.200000   0.410000   0.610000 (  0.647752)
<<  0.000000   0.010000   0.010000 (  0.006152)
+  0.200000   0.410000   0.610000 (  0.641464)
#{}  0.210000   0.430000   0.640000 (  0.682333)

数値だけ比較すると,jrubyの圧勝のように見えるけれど,体感は,CRuby のほうが早い.たぶん,Jrubyのsystemが0になっていることが影響しているのではという気がする.
文字列周りは,JRubyの下層にあるJavaのレベルでJDKバージョンによって様々だったり*1するので,今後どうなるのかは判らない.
けれど,<< が異様に速いのは,CRuby / JRuby の両方で出ていることから,Rubyの言語仕様が絡んでいる…のか?*2

結論としては,文字列の追加処理は << を使っておくのが現時点では安心,ということ?

*1:StringBufferを使えだの,いや最近のコンパイラでは使わないほうが早いだの

*2:私は ruby 詳しく無いので判らない