Bye Bye Moore

PoCソルジャーな零細事業主が作業メモを残すブログ

EnumerableにProc渡すなら外で作ってからにしましょう

Enumrableにprocオブジェクトを渡すとメソッドを渡した時のように実行してくれます。
このとき、procオブジェクト自体は外に出すか、素直にブロックを使うようにしないと、かなり遅くなります。

実際のところ

0を渡すと「A:1」、5なら「F:6」という感じの文字列を返す処理を考えます。
実装として、

  • procを外でつくって渡す
  • procを毎回生成して渡す
  • ブロック内に記述する

の三パターンを考えてみます。
ベンチマークは以下の通り。

require 'benchmark/ips'

Benchmark.ips do |x|
  proc =-> x { %Q(#{(0x41 + x).chr}:#{x.next})}
  x.report('proc') { [1,2,3].map(&proc)}
  x.report('proc(arvg)') { [1,2,3].map(&->x{%Q(#{(0x41 + x).chr}:#{x.next})})}
  x.report('block') { [1,2,3].map{|x| %Q(#{(0x41 + x).chr}:#{x.next}) } }
  x.compare!
end

さっそく実行してみると……

Calculating -------------------------------------
                proc    35.663k i/100ms
          proc(arvg)    22.353k i/100ms
               block    36.191k i/100ms
-------------------------------------------------
                proc    518.657k (±10.2%) i/s -      2.568M
          proc(arvg)    349.988k (±11.1%) i/s -      1.744M
               block    515.502k (± 9.0%) i/s -      2.570M

Comparison:
                proc:   518656.8 i/s
               block:   515502.0 i/s - 1.01x slower
          proc(arvg):   349987.6 i/s - 1.48x slower

毎回渡すパターンがすごい結果になりました。
……遅いとはわかっていましたが、まさかここまでとは……
二倍したものをmapするといったような単純な処理の場合、この差はさらに広がります。
なので、procを渡す場合は変に色気を出さないで外に出したほうがいいです。