※古いRuby(1.9)のお話、新しいバージョンではマイナス乗とか気にしなくていい!
目次
前置き
n = 10 2 ** n
2のn乗。オーソドックスな書き方だ。
しかしRubyにおいて2のn乗を普通に書くのは
速度的にはあんましえらくない。とされている*1
1 << n
このように「ビットシフト」を用いて、「2のn乗」を高速化できる。
require "benchmark" n = 32 Benchmark.bm(4) do |r| r.report("**") { 10_000_000.times { 2 ** n } } r.report("<<") { 10_000_000.times { 1 << n } } end # user system total real # ** 1.438000 0.000000 1.438000 ( 1.434798) # << 1.093000 0.000000 1.093000 ( 1.087205)
しかし、このビットシフトだと「小数点乗」とか「マイナス乗」が再現できない。
# これはできる p 2 ** 3 # 8 p 1 << 3 # 8 # ここからおかしくなる p 2 ** 3.5 # 11.313708498984761 p 1 << 3.5 # 8 p 2 ** -3 # (1/8) p 1 << -3 # 0
じゃ~しょうがない
諦めて「**」で実装するかとやってみると
require "benchmark" Benchmark.bm(4) do |r| r.report("<<") { 10_000_000.times { 1 << 5 } } r.report("**") { 10_000_000.times { 2 ** -5 } } end # user system total real # << 0.468000 0.000000 0.468000 ( 0.465533) # ** 3.016000 0.000000 3.016000 ( 3.014047)
何これ遅ッッッッッッッ
改善
x = 2 # 底 y = -5 # 指数 y >= 0 ? x ** y : 1.0 / (x ** y.abs)
とりあえずこんな感じの式で速くできる。
(元と違って分数クラスは中継しない)
検証↓
require "benchmark" x = 2 y = -5 Benchmark.bm(4) do |r| r.report("**") { 10_000_000.times { x ** y } } r.report("kai"){ 10_000_000.times { y >= 0 ? x ** y : 1.0 / (x ** y.abs) } } end # user system total real # ** 3.125000 0.000000 3.125000 ( 3.129178) # kai 1.157000 0.000000 1.157000 ( 1.158395)
まだ遅いけど相当マシ。
結論
古いRubyつらたん。
用意が面倒なのでWandboxを使って
Ruby 2.7.3辺りで同じことを試してみたが
こんなややこしいことをしなくても普通に速い。
余談
2^nは「<<」で書けという話だったが
2^5くらいだとなぜか「**」の方が速い逆転現象が起こる(?)
require "benchmark" n = 5 Benchmark.bm(4) do |r| r.report("<<") { 10_000_000.times { 1 << n } } r.report("**") { 10_000_000.times { 2 ** n } } end # user system total real # << 0.500000 0.000000 0.500000 ( 0.503168) # ** 0.484000 0.000000 0.484000 ( 0.483344)
そして、「自作のBenchmark」で同じことをやると逆転しない。
(色々と仕様違うけど気にしないで)
module MyBenchmark class << self def run(count = 1) @count = count yield self if block_given? end def report(name = nil) if block_given? time = Time.now t = 0 while t < @count yield t += 1 end r = Time.now - time puts "#{name ? "#{name}: " : ""}#{r} s / #{(r / @count.to_f) * 6000} %" end end end end MyBenchmark.run(10_000_000) do |r| r.report("<<") { 1 << n } r.report("**") { 2 ** n } end # <<: 0.562924 s / 0.0003377544 % # **: 0.618259 s / 0.0003709554 %
どっちを信じればいいんだろう?
まぁ、少なくとも「左シフト」に関しては使っていいと思う
右シフトは低い数だと本当に要らない。
require "benchmark" Benchmark.bm(2) do |r| r.report(">>") { 10_000_000.times { 32 >> 5 } } r.report("/") { 10_000_000.times { 32 / 32 } } end # user system total real # >> 0.469000 0.000000 0.469000 ( 0.470105) # / 0.312000 0.000000 0.312000 ( 0.322468)
*1:「**」は2^30あたりから急に遅くなる