ruby と jruby の速度の差

先日の find_allのindex版( http://qiita.com/pocari/items/0b4e632ef2a2b6a97ba9 )という記事を受けて。

* ruby 1.9
* ruby 2.0
* ruby 2.1
* jruby1.7.12 --1.9
* jruby1.7.12 --2.0

の5つの環境で速度がどう変わるか調べてみたよ。

## わかったこと

※ すべて、今回のベンチマークの範囲の話。一般的かどうかはわからない。

* `each_with_index.select` や `flat_map.with_index` は、ruby 1.9 → 2.0 → 2.1 と、すごく速くなっている。都合 10倍以上。
* CRuby だと、`each_with_index` と `each.with_index` は、同じっぽい。
* JRuby だと、`each_with_index` より `each.with_index` がの方が速い。
* `flat_map.with_index{}` より、`map.with_index{}.compact` の方が速かった。意外。
* `--1.9` と `--2.0` の速度差はほとんどない。


## 結果

横軸は時間なので、長いほうが遅い。
![bench_graph.png](https://qiita-image-store.s3.amazonaws.com/0/6854/7fe4f7b5-b1bd-f351-91e3-dbe50ed3be1e.png "bench_graph.png")

### ruby 1.9

```text:
"ruby 1.9.3p545 (2014-02-24 revision 45159) [x86_64-darwin13.0.0]"
size : 5000000
                                   user     system      total        real
     each_with_index.select:   7.130000   0.010000   7.140000 (  7.145922)
     each.with_index.select:   7.160000   0.010000   7.170000 (  7.170584)
   map.with_index{}.compact:   0.580000   0.030000   0.610000 (  0.628142)
        flat_map.with_index:   7.180000   0.010000   7.190000 (  7.189736)
          size.times.select:   0.430000   0.000000   0.430000 (  0.433888)
```

### ruby 2.0

```text:
"ruby 2.0.0p481 (2014-05-08 revision 45883) [x86_64-darwin13.3.0]"
size : 5000000
                                   user     system      total        real
     each_with_index.select:   3.930000   0.010000   3.940000 (  3.936129)
     each.with_index.select:   3.980000   0.000000   3.980000 (  3.984897)
   map.with_index{}.compact:   0.560000   0.030000   0.590000 (  0.609218)
        flat_map.with_index:   3.950000   0.000000   3.950000 (  3.954283)
          size.times.select:   0.410000   0.000000   0.410000 (  0.411161)
```

### ruby 2.1

```text:
"ruby 2.1.2p95 (2014-05-08 revision 45877) [x86_64-darwin13.0]"
size : 5000000
                                   user     system      total        real
     each_with_index.select:   0.690000   0.000000   0.690000 (  0.683463)
     each.with_index.select:   0.690000   0.000000   0.690000 (  0.693749)
   map.with_index{}.compact:   0.510000   0.030000   0.540000 (  0.556931)
        flat_map.with_index:   0.680000   0.000000   0.680000 (  0.679448)
          size.times.select:   0.380000   0.000000   0.380000 (  0.380963)
```

### jruby1.7.12 --1.9

```text:
"jruby 1.7.12 (1.9.3p392) 2014-04-15 643e292 on Java HotSpot(TM) 64-Bit Server VM 1.7.0_07-b10 [darwin-x86_64]"
size : 5000000
                                   user     system      total        real
     each_with_index.select:   1.670000   0.020000   1.690000 (  1.466000)
     each.with_index.select:   0.860000   0.000000   0.860000 (  0.829000)
   map.with_index{}.compact:   0.620000   0.010000   0.630000 (  0.540000)
        flat_map.with_index:   0.780000   0.000000   0.780000 (  0.709000)
          size.times.select:   0.430000   0.000000   0.430000 (  0.368000)
```

### jruby1.7.12 --2.0

```text:
"jruby 1.7.12 (2.0.0p195) 2014-04-15 643e292 on Java HotSpot(TM) 64-Bit Server VM 1.7.0_07-b10 [darwin-x86_64]"
size : 5000000
                                   user     system      total        real
     each_with_index.select:   1.630000   0.020000   1.650000 (  1.427000)
     each.with_index.select:   0.880000   0.010000   0.890000 (  0.853000)
   map.with_index{}.compact:   0.630000   0.010000   0.640000 (  0.528000)
        flat_map.with_index:   0.760000   0.000000   0.760000 (  0.710000)
          size.times.select:   0.390000   0.000000   0.390000 (  0.358000)
```

## 利用したプログラム

```ruby
p RUBY_DESCRIPTION
require "benchmark"

def run_bench( size )
  puts "size : #{size}"
  ary = Array.new(size){ rand(size) }
  Benchmark.bm(28) do |x|
    def x.rep(s); report( "%28s" % s ){ yield }; end
    r=[]
    x.rep("each_with_index.select:"){
      r<<ary.each_with_index.select{ |v,ix| v<5 }.map(&:last)
    }
    x.rep("each.with_index.select:"){
      r<<ary.each.with_index.select{ |v,ix| v<5 }.map(&:last)
    }
    x.rep("map.with_index{}.compact:"){
      r<<ary.map.with_index{ |v,ix| v<5 ? ix : nil }.compact
    }
    x.rep("flat_map.with_index:"){
      r<<ary.flat_map.with_index{ |v,ix| v<5 ? [ix] : [] }
    }
    x.rep("size.times.select:"){
      r<<ary.size.times.select{ |i| ary[i]<5 }
    }
    puts "**NG**" unless r.uniq.size==1
  end
end

run_bench( 5000000 )
```

## 結論

* `each_with_index` よりも `each.with_index` を使ったほうがいいかもね。
* ruby 2.1 は素晴らしいね！
* 開発者の皆様 ありがとう！