Float#ceil vs Integer#divmod

とあるアプリを書いているときに、
「ある個数の要素の集まりを、一定の数単位で分ける。
その単位に足りない余りも、分けられた一つとして数える。
するといくつに分けられるか」

という処理を書くことになりました。
我ながら日本語が実にヘタだ。つまり例としては、110個の蜜柑を20個入りの箱かなんかに詰めていく、と。んでもって満杯の箱5個+中途半端な10個入りの箱1個=計6個、というような数え方がしたかったのです。

わたしは初め、次のように書きました。

n = (hoge_size.to_f / unit_size).ceil

でもってアプリは正常に動きました。

しかし、わたしはここで色気を出して「高速化してやろう」と思ったのです。
なにせFloatです、Float。きっと重いに違いない。
というわけで、次のようなことを考えました。

n, r = hoge_size.divmod(unit_size)
n += 1 unless r.zero?

整数しか出てきていません。おまけにBignumの範囲を扱うことなどありえません。これは速くなるんじゃないか?

しかし、結果は。

require 'benchmark'

N_VIEW_ENTRY = 20

def measure_f(x)
  (x.to_f / N_VIEW_ENTRY).ceil
end

def measure_i(x)
  n, r = x.divmod(N_VIEW_ENTRY)
  n += 1 unless r.zero?
  n
end

n_try = 100000

Benchmark.bmbm do |job|
  job.report 'by Float#ceil' do
    n_try.times{|n| measure_f(n) }
  end
  job.report 'by Integer#divmod' do
    n_try.times{|n| measure_i(n) }
  end
end

このベンチに対して

Rehearsal -----------------------------------------------------
by Float#ceil       0.219000   0.000000   0.219000 (  0.250000)
by Integer#divmod   0.328000   0.000000   0.328000 (  0.343000)
-------------------------------------------- total: 0.547000sec

                        user     system      total        real
by Float#ceil       0.218000   0.000000   0.218000 (  0.235000)
by Integer#divmod   0.329000   0.000000   0.329000 (  0.359000)

こんな有様でした。

よく考えれば、後者だってdivmodがArrayをいちいち作ってるわけで…。

それに加え、この処理、1回のリクエストに対して1回しか実行されない類のものでして…。
こんなとこが高速化できたってしょうもないというのはわかっていたのですが、「高速化」という響きに負けてつい取り組んでしまったのでした。