動機
Ruby で 0 から 9 までの整数の乱数を発生させるのは
Random.rand(10)
でいいですよね。
(ええと,Kernel.#rand
を使って rand(10)
とすることもできますが,その話はまたあとで)
では,範囲が「0 から N-1 まで」ではない場合,どう書くのがいいでしょうか。
たとえば -1, 0, 1 のどれかをランダムに発生させるのに
Random.rand(3) - 1
と書けますが,このコードを見て -1, 0, 1 だと分かるまで一瞬考えちゃいますし,なんだかスマートではありません。
リファレンスマニュアルで Random.rand
を見れば,
Random.rand(-1..1)
と書けることが分かります。こっちのほうが Ruby らしいですし,分かりやすいように思います。
と・こ・ろ・が!
後者は遅いんですよぉ。(今日まで知らなかったケド)
計測
ベンチマークテストをやってみましょう。
ついでに,Random.rand
と Kernel.#rand
の比較もやってみます。
require 'benchmark'
iter = 10_000_000
Benchmark.bmbm do |rep|
rep.report "Random.rand(3) - 1" do
iter.times do
v = Random.rand(3) - 1
end
end
rep.report "Random.rand(-1..1)" do
iter.times do
v = Random.rand(-1..1)
end
end
rep.report "rand(3) - 1" do
iter.times do
v = rand(3) - 1
end
end
rep.report "rand(-1..1)" do
iter.times do
v = rand(-1..1)
end
end
end
結果はこんな感じ:
user system total real
Random.rand(3) - 1 1.280000 0.000000 1.280000 ( 1.295193)
Random.rand(-1..1) 3.200000 0.020000 3.220000 ( 3.235467)
rand(3) - 1 2.190000 0.010000 2.200000 ( 2.207524)
rand(-1..1) 2.440000 0.010000 2.450000 ( 2.465236)
環境は
ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin15]
です。
なんで?
Random.rand
の場合,Range
を渡すとメチャメチャ遅い。
Kernel.#rand
だと,それほどの差は無く,Random.rand
の二つの結果のちょうど間に収まっています。ここがちょっと意外でした。
Random.rand
と kernel.#rand
って,中身は同じだと勝手に思い込んでましたが,違うのね。
引数に Range
を渡す方式だと遅くなるのは,たぶんこういうことでしょう。
Range
が渡されると,その始端と終端をまず求め,その差分を取ります。この値を上限値とする乱数をまず発生させます。その値に始端の値を足します。
始端と終端から差分をとる処理が,メソッド呼び出しのたびに毎回発生するのが無駄になっているワケですね。
大量の乱数を発生させるときは要注意です。