Ruby

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) を実行すれば良い話だった気がします。