Why is Ruby's Hash#values faster than Hash#each_value in certain cases?

Viewed 627

When I apply each_value to a hash, it takes significantly longer than when I use values, even though each_value ostensibly avoids allocating and copying an array.

I wrote a simple comparison:

require 'benchmark/ips'

some_hash = File.open('with_an.dat') { |f| Marshal.load f }

Benchmark.ips do |x|
  x.report "calling each_value" do
    some_hash.each_value
  end
  x.report "calling values" do
    some_hash.values
  end
  x.compare!
end

Benchmark.ips do |x|
  x.report "summing each_value" do
    some_hash.each_value.inject &:+
  end
  x.report "summing values" do
    some_hash.values.inject &:+
  end
  x.compare!
end

And here were the results:

Calculating -------------------------------------
  calling each_value    58.166k i/100ms
      calling values     2.000  i/100ms
-------------------------------------------------
  calling each_value      1.312M (±40.7%) i/s -      5.468M
      calling values     29.423  (±10.2%) i/s -    146.000 

Comparison:
  calling each_value:  1312156.6 i/s
      calling values:       29.4 i/s - 44596.28x slower

Calculating -------------------------------------
  summing each_value     1.000  i/100ms
      summing values     1.000  i/100ms
-------------------------------------------------
  summing each_value      2.107  (± 0.0%) i/s -     11.000 
      summing values      8.002  (±12.5%) i/s -     40.000 

Comparison:
      summing values:        8.0 i/s
  summing each_value:        2.1 i/s - 3.80x slower

As expected, just calling each of the methods, each_value was much faster, since it only has to create an Enumerator, and doesn't actually traverse the hash table. Meanwhile, values has to copy the entire array.

Yet, when I add the values up, it appears that the each_value approach is 3x slower than the values approach. Why would that be?

2 Answers
Related