Ruby の Range#include? を使うよりも Range#cover? を使うほうが高速になることがある
次のように Date
な Range
では #include?
よりも #cover?
の方が高速で動作します。
require 'benchmark' require "date" range = (Date.parse("2020/01/01")..Date.parse("2021/01/01")) date = Date.parse("2020/05/01") Benchmark.bm(20) do |b| b.report('Date include?') { 10_000.times { range.include?(date) } } b.report('Date cover?') { 10_000.times { range.cover?(date) } } end __END__ user system total real Date include? 0.720505 0.000000 0.720505 ( 0.720700) Date cover? 0.001932 0.000000 0.001932 ( 0.001932)
なぜパフォーマンスに差が出るのか
これは Range#include?
が Range
を『離散値』として扱うのに対して Range#cover?
は『連続値』として扱うためです。
内部的な挙動でいうと Range#include?
は Enumerable#include?
を呼び出しており線形的に値を探査します。
一方で Range#cover?
は始端と終端を <=>
で比較しているだけなのでより高速に動作します。
なので Range
が『連続値』であることが保証されているのであれば Range#cover?
を使ったほうがより高速に動作します。
require 'benchmark' require "date" range = (Date.parse("2020/01/01")..Date.parse("2021/01/01")) date = Date.parse("2020/05/01") Benchmark.bm(20) do |b| b.report('Date include?') { 10_000.times { range.include?(date) } } # Range#include? は実質 Enumerable#include? と同じ b.report('Date each.include?') { 10_000.times { range.each.include?(date) } } end __END__ user system total real Date include? 0.730970 0.000163 0.731133 ( 0.731358) Date each.include? 0.743971 0.000008 0.743979 ( 0.744230)
ちなみに Range#include?
と Range#cover?
で挙動が違うケースもあるので注意しましょう。
p ("a" .. "c").include?("ba") # => false p ("a" .. "c").cover?("ba") # => true # これは # "a" <=> "ba" # => -1 # "c" <=> "ba" # => 1 # となるため "a" .. "c" の範囲に含まれてしまう
Range が数値だった場合は?
ちなみに Range
の要素が数値の場合は #include?
は #cover?
と同等の動きがするのでパフォーマンス的な懸念点はありません。
require 'benchmark' require "date" range = (Date.parse("2020/01/01")..Date.parse("2021/01/01")) date = Date.parse("2020/05/01") Benchmark.bm(20) do |b| b.report('Integer include?') { 10_000.times { (1..1000).include?(500) } } b.report('Integer cover?') { 10_000.times { (1..1000).cover?(500) } } end __END__ user system total real Integer include? 0.001725 0.000000 0.001725 ( 0.001724) Integer cover? 0.001854 0.000000 0.001854 ( 0.001855)