[Ruby] Is Range#cover? or Range#include? or ActiveSupport::TimeWithZone#between? or faster?

1 minute read

rubocop-performance を確認していたら、

Performance/RangeInclude: Use Range#cover? instead of Range#include?.

が出たのでベンチマークを取ってみました。

require 'bundler/setup'
require 'benchmark_driver'

Benchmark.driver do |x|
  x.prelude <<~RUBY
  require 'active_support/time'
  t = Time.current + 5.minutes
  RUBY

  x.report 'include?', %[ (Time.current..Float::INFINITY).include?(t) ]
  x.report 'cover?', %[ (Time.current..Float::INFINITY).cover?(t) ]
  x.report 'between?', %[ t.between?(Time.current, Float::INFINITY) ]
  x.report 'raw_function', %[ t >= Time.current && t <=  Float::INFINITY ]
end

結果

Warming up --------------------------------------
            include?    85.071k i/s -     86.456k times in 1.016282s (11.75μs/i)
              cover?   131.933k i/s -    135.600k times in 1.027793s (7.58μs/i)
            between?   276.108k i/s -    285.615k times in 1.034434s (3.62μs/i)
        raw_function   285.200k i/s -    291.396k times in 1.021725s (3.51μs/i)
Calculating -------------------------------------
            include?   125.985k i/s -    255.212k times in 2.025732s (7.94μs/i)
              cover?   133.774k i/s -    395.799k times in 2.958713s (7.48μs/i)
            between?   304.505k i/s -    828.322k times in 2.720227s (3.28μs/i)
        raw_function   304.348k i/s -    855.600k times in 2.811255s (3.29μs/i)

Comparison:
            between?:    304504.7 i/s
        raw_function:    304348.1 i/s - 1.00x  slower
              cover?:    133774.0 i/s - 2.28x  slower
            include?:    125985.1 i/s - 2.42x  slower

include? から cover? に変更したほうが早いが、between? や便利機能使わずに普通に書いた raw_function の方が早くなりました。 後者の2つは実行するタイミングで順位がわかりましたが、ほぼ誤差の範囲だと思います。

ちなみに benchmark_driver では、 ymlで入力することもサポートしているので、

# range.yml
prelude: |
  require 'bundler/setup'
  require 'active_support/time'
  t = Time.current + 5.minutes

benchmark:
  include?: (Time.current..Float::INFINITY).include?(t)
  cover?: (Time.current..Float::INFINITY).cover?(t)
  between?: t.between?(Time.current, Float::INFINITY)
  raw_function: t >= Time.current && t <= Float::INFINITY

のようなファイルを用意して、

$ benchmark-driver range.yml

と書いても似たような結果を得られました。