注意: この記事はRuby2.3系の頃の話で、現在(2021/05)のAtCoderはruby 2.7系のため、同じ挙動ではないかもしれません。
競プロで避けたい2つのメソッド
ABC 147Dで痛い目を見たのでまとめておきます。
https://atcoder.jp/contests/abc147/tasks/abc147_d
each_with_index
ブロックの外で変数を宣言し、自分でインクリメントしたほうが早い。
596.8 i/s
(each_with_index) vs 704.4 i/s
(ブロック外変数をインクリメント)
(秒間何回処理を実行できるかの比較)
to_s(2)
Integer#to_s は引数の進数展開してくれますが、これが遅かった。
全bitを確認する場合でも、bit shiftしながら確認していったほうが早いです。
1783.4 i/s
(while loop) vs 704.4 i/s
(to_s(2))
実行コード
require 'benchmark/ips'
cons = 100.times.with_object([]) { |_i, obj| obj << rand(1 << 60) }
ary_to_s1 = Array.new(60, 0)
ary_to_s2 = Array.new(60, 0)
ary_while = Array.new(60, 0)
ONE = '1'.freeze
Benchmark.ips do |x|
x.config(:time => 20, :warmup => 0)
x.report("each_with_index") {
cons.each do |num|
num.to_s(2).chars.reverse.each_with_index do |x, i|
ary_to_s1[i] += 1 if x == ONE
end
end
}
x.report("index") {
cons.each do |num|
index = 0
num.to_s(2).chars.reverse.each do |x|
ary_to_s2[index] += 1 if x == ONE
index += 1
end
end
}
x.report("while") {
cons.each do |num|
tmp = num
ind = 0
while tmp > 0
ary_while[ind] += tmp%2
tmp = tmp >> 1
ind += 1
end
end
}
x.compare!
end
Comparison:
while: 1783.4 i/s
index: 704.4 i/s - 2.53x slower
each_with_index: 596.8 i/s - 2.99x slower
Ruby 2.3.3
で確認