Rubyの文字列の連結速度を測定してGruffで表示してみた

String と StringBuffer の文字列連結速度のテスト - すえひろがりっっっっ!と同様小ネタ。


社会人になって1年たちますが、仕事で「素人かよ!」って思うコードをよく見かけます。
そのトップ10に入るのが「Javaで+= で文字列を連結していてパフォーマンスがすこぶる悪い」コードです。
これをやってた人は、いまだに += で文字列を連結しているコードをよこしてきます。
どうにかならないもんだろうか。


ところで、Ruby でも String の連結は重いのだよね?とふと思ったので += と << で結合した場合の速度を測定してみました。
楽しいRubyによると Ruby でも += で結合するとインスタンスを生成するらしい。
ということは += と << では性能に差が出るはず。


せっかくRubyなのでGruffを試してみます。

バージョン

Mac OS X 10.6.3
CPU 3.06 GHz Intel Core 2 Duo
メモリ 8GB
ruby 1.8.7
gruff 0.3.6

実験コード

require 'rubygems'
require 'gruff'
require 'benchmark'
# 最大結合回数
MAX = 100000
# 刻み
STEP = 1000 

g = Gruff::Line.new

# 指定したやり方で 0 〜 10万回の文字列結合にかかる時間を計測する
# 返り値は計測した時間の配列
def bench
  benchmarks = []
  0.step(MAX, STEP) do |i|
    benchmarks << Benchmark.measure {
      str = ''
      i.times do
        str = yield str
      end
    }
  end
  benchmarks.collect{|b| b.real}
end

g.theme_37signals
#  += で結合
g.data("+=", bench{|str| str += 'a'})
#  concat で結合
g.data("concat", bench{|str| str.concat('a')})
# << で結合
g.data("<<", bench{|str| str << 'a'})
0.step(MAX, MAX / 4) do |i|
  g.labels.merge!({i/STEP => i.to_s})
end
g.x_axis_label = 'Times'
g.y_axis_label = 'seconds'
g.write('result.png')
  • "+=" と "concat" と "<<" で結合した場合を計測しています。
  • 計測方法を外から指定するのに高階関数にしたかったのだけど、Ruby はそこで yield 使ってブロックを渡すのだった、ということで Ruby二年目にして初 yield。書き方あってるだろうか。。。
  • 0〜10万回まですべて試すと時間がかかりすぎるので 1000回刻みにしてます。それでも結構かかる。

結果

こんな感じ。Gruffで描画するグラフは綺麗だぁ!

横軸が結合の回数、縦軸がかかった時間(秒)です。
"<<"の緑色はほぼ隠れてしまっていますが、"concat"の青色に上書きされていますorz

やっぱり += は圧倒的に遅い。
きっとJavaと同様、インスタンス生成分の差ですね。

結論

  • たくさん "+=" したいときは、"<<"を使うようにしよう。
  • Gruff はグラデーションかかるし綺麗なグラフが簡単に描ける