TL;DR
Rubyでは i++
ができない。
たまたま i++
しようとしたらエラーになったのでそういえばインクリメント演算子がRubyだと出来なかった気がするなと思い、念のために調べてみた。
公式ドキュメントじゃないけど、割と自明な話しだと思うので公式ではなく個人のテックブログを参照した。詳細に調べられていたので問題ないかなと思う。
そして恐らくだが以前も同じブログを参照した気がする。
選択肢としてはi += 1
とi.succ
、i.next
の3パターンあるがなにが違うのか気になったので調べたところ先人がすでに調べていた。
ただこの記事も前述の記事も2014年〜2015年なので現在のRubyバージョンでは状況が変わっている可能性があるし、なんなら自分が問題を起こしたバージョン(ruby@2.4.1p111)とで結果が異なる可能性があるのではないか?と思い、せっかくなので調べてみた。
結論:
3つの中でsucc
が比較的安定、速い。
次点でi += 1
、next
はFixnumで他に比べて遅い。
Rubyでインクリメントしたいとき、普段は i += 1
か i.succ
を使う。
パフォーマンスが気になる箇所では i.succ
を使うのがベター。次点でi += 1
、i.next
を使ってたらレビューで指摘してあげたほうがいいかもしれない……くらいかな。
2つ目のブログで書かれている状態とほぼ同じ結果になったのでおそらくは原因も同じと推測される。
各バージョンのコードまでは検証するつもりがなかったので省略してるけど多分同じなんだろうと思う。
検証環境:
MacOS version 10.14.5(18F132)
MacBook Pro (Retina, 15-inch, Late 2013)
プロセッサ 2.3 GHz Intel Core i7
memory 16 GB 1600 MHz DDR3
検証項目:
各バージョンの最新 + 問題を起こしたバージョンで検証。
本来は何度か実行してその平均値で出すんだろうけどメンドイので1回しかやってないので何かの検証目的でいる場合はサンプルデータ1くらいの扱いでお願いします。
検証コードは2つ目のブログの検証コードをそのまま流用。
理由としては計測する方法が変わると期待する結果も変わってしまう可能性があるため、できるだけ条件を同じにしたかったから。
実行したコード(ファイル名が違うだけ)
require 'benchmark'
Benchmark.bm do |x|
cnt = 100000000
fixnum = 1
bignum = 1 << 64
x.report("+=1 Fixnum") do
a = fixnum
cnt.times{ a += 1 }
end
# Fixnum#succ
x.report("Fixnum succ") do
a = fixnum
cnt.times{ a = a.succ }
end
# Fixnum#next
x.report("Fixnum next") do
a = fixnum
cnt.times{ a = a.next }
end
x.report("+=1 Bignum") do
a = bignum
cnt.times{ a += 1 }
end
# Bignum#succ
x.report("Bignum succ") do
a = bignum
cnt.times{ a = a.succ }
end
# Bignum#next
x.report("Bignum_next") do
a = bignum
cnt.times{ a = a.next }
end
end
ruby@2.4.1p111
Fixnumのときのnext
だけが遅い。ブログの現象と同じ。
$ ruby example.rb
user system total real
+=1 Fixnum 4.720000 0.000000 4.720000 ( 4.724997)
Fixnum succ 4.720000 0.000000 4.720000 ( 4.727951)
Fixnum next 5.870000 0.010000 5.880000 ( 5.879860)
+=1 Bignum 10.570000 0.000000 10.570000 ( 10.582265)
Bignum succ 10.340000 0.010000 10.350000 ( 10.354154)
Bignum_next 10.110000 0.010000 10.120000 ( 10.121698)
ruby@2.6.3
Fixnumでnext
だけが遅いという現象が発生してたのでこれはなにか別のアプリケーションが影響してるのか?と思ってもう一回実行したんだけど結果ほぼ変わらずだったのでどうやらnext
がFixnumのときだけ遅いっぽい。
$ ruby example.rb
user system total real
+=1 Fixnum 5.052053 0.007104 5.059157 ( 5.068701)
Fixnum succ 4.829195 0.004266 4.833461 ( 4.840140)
Fixnum next 6.159282 0.002336 6.161618 ( 6.165148)
+=1 Bignum 10.592028 0.004909 10.596937 ( 10.601052)
Bignum succ 10.377451 0.003914 10.381365 ( 10.384787)
Bignum_next 10.320459 0.003760 10.324219 ( 10.327414)
ruby@2.5.5
こちらもブログの現象と同じといえる。
$ ruby example.rb
user system total real
+=1 Fixnum 4.719072 0.001677 4.720749 ( 4.722440)
Fixnum succ 4.651602 0.001740 4.653342 ( 4.654881)
Fixnum next 5.748883 0.001787 5.750670 ( 5.752244)
+=1 Bignum 10.454205 0.007352 10.461557 ( 10.471455)
Bignum succ 10.872434 0.028426 10.900860 ( 10.935718)
Bignum_next 10.624259 0.024823 10.649082 ( 10.674851)
ruby@2.4.6
ブログの(ry
ruby example.rb
user system total real
+=1 Fixnum 4.430000 0.000000 4.430000 ( 4.437439)
Fixnum succ 4.570000 0.020000 4.590000 ( 4.615233)
Fixnum next 5.950000 0.020000 5.970000 ( 6.008219)
+=1 Bignum 10.600000 0.030000 10.630000 ( 10.660327)
Bignum succ 10.050000 0.020000 10.070000 ( 10.093179)
Bignum_next 10.370000 0.020000 10.390000 ( 10.410649)
ruby@2.3.7
たまたまなのかもしれないけど一番古いRubyのバージョンが一番遅いだろうとなんとなく考えていたが結果が真逆だったのが面白い。
昔のほうが特定の条件下では高速に動作するのか。
とはいえそのためにあえて2.3.7
使うひとはいないと思うけど、どうしても速度がほしいなら別の言語選ぶだろうしな。
ruby example.rb [05 30 00:37:43]
user system total real
+=1 Fixnum 4.170000 0.000000 4.170000 ( 4.173693)
Fixnum succ 4.150000 0.000000 4.150000 ( 4.154439)
Fixnum next 5.280000 0.000000 5.280000 ( 5.282451)
+=1 Bignum 9.170000 0.000000 9.170000 ( 9.176064)
Bignum succ 9.230000 0.010000 9.240000 ( 9.233617)
Bignum_next 9.420000 0.000000 9.420000 ( 9.428198)