Sabri Tuzcu

Benchmarking each_with_object Against inject when building Hashes from Arrays

I’d long known that using Ruby’s Hash#merge! rather than Hash#merge was much faster: merge hash in place in memory, don’t copy and assign. I’d never come across each_with_index in the wild, at least and remembered.

What a fool I’ve been.

Rather than use code like either of these…

array_of_stuff.inject({}) do |result, element|
  result[] = element.value

array_of_stuff.inject({}) do |result, element|
  result.merge!( => element.value)

…it’s much more idiomatic Ruby to use each_with_object.

array_of_stuff.each_with_object({}) do |element, result|
  result[] = element.value

I was interested to see how this idiomatic Ruby performed. I put together a little script to test the various ways of generating a Hash from an decent-sized array of simple Struct-based objects. I used the benchmark-ips gem.

require 'benchmark/ips'

User =, :stuff)
a = { |i|, stuff: rand(1000)) }

Benchmark.ips do |x|'assign&return') do
    a.inject({}) { |memo, i| memo[] = i.stuff; memo }
  end'merge') do
    a.inject({}) { |memo, i| memo.merge( => i.stuff) }
  end'merge!') do
    a.inject({}) { |memo, i| memo.merge!( => i.stuff) }
  end'map with tuples') do { |i| [, i.stuff] }.to_h
  end'each_with_object') do
    a.each_with_object({}) { |i, memo| memo[] = i.stuff }

The results were interesting.

assign&return 3136.7(±8.2%) i/s
merge 5.9(±0.0%) i/s
merge! 1168.0(±28.3%) i/s
map with tuples 2400.8(±23.0%) i/s
each_with_object 3220.8(±3.3%) i/s

Turns out the most idiomatic code is also the fastest. Followed surprisingly closely by the ‘do the simplest thing’ variant, but not by a huge amount.

PS If you’re using merge without the ! inside loops like this… just don’t.

Sign up to get a nugget of Ruby knowledge every couple of weeks or so.

Last updated on October 21st, 2014