Posted at

Ruby の Digest(name) にかかるコストを把握してなかった

More than 1 year has passed since last update.

以下の問題に遭遇して以来、ダイジェスト値を計算する際に Digest(:MD5).hexdigest(str) と書く様にしていました。autoload かどうかに関わらず…。

単純に毎回 Digest(name) 関数を実行していると、複数回繰り返し呼ばれる様な場合に著しく時間がかかる事を、最近ようやく把握しました…。

require 'digest/md5'

require 'benchmark'

N = 100_000
nums = 1.upto(N).map(&:to_s)

Benchmark.bm(15) do |x|
x.report('Digest::MD5') do
nums.each { |num| Digest::MD5.hexdigest(num) }
end

x.report('memoized') do
digest = Digest(:MD5)
nums.each { |num| digest.hexdigest(num) }
end

x.report('Digest(:MD5)') do
nums.each { |num| Digest(:MD5).hexdigest(num) }
end
end

__END__
user system total real
Digest::MD5 0.170000 0.010000 0.180000 ( 0.182523)
memoized 0.130000 0.000000 0.130000 ( 0.132090)
Digest(:MD5) 8.120000 7.750000 15.870000 ( 42.705663)

Digest(name) 関数の実装は以下の通り。

# https://github.com/ruby/ruby/blob/trunk/ext/digest/lib/digest.rb#L96-L109

def Digest(name)
const = name.to_sym
Digest::REQUIRE_MUTEX.synchronize {
# Ignore autoload's because it is void when we have #const_missing
Digest.const_missing(const)
}
rescue LoadError
# Constants do not necessarily rely on digest/*.
if Digest.const_defined?(const)
Digest.const_get(const)
else
raise
end
end

require 'digest/md5' してから Digest::MD5.hexdigest(str) を実行すれば良い話だった気がします。