fastruby / fast-ruby

:dash: Writing Fast Ruby :heart_eyes: -- Collect Common Ruby idioms.
https://github.com/fastruby/fast-ruby
5.67k stars 376 forks source link

Hash#merge({}) vs Hash#dup#merge!({}) vs {}#merge!(Hash) #42

Closed bejmuller closed 9 years ago

bejmuller commented 9 years ago

In some cases we want duplicates of hashes to be created while merging, so I was wondering which one is the fastest way of achieving this:

$ ruby -v code/hash/merge-vs-dup-merge-bang-vs-merge-bang.rb 
ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-linux]
Calculating -------------------------------------
      Hash#merge({})   603.000  i/100ms
 Hash#dup#merge!({})   650.000  i/100ms
     {}#merge!(Hash)     1.763k i/100ms
-------------------------------------------------
      Hash#merge({})      6.593k (± 7.6%) i/s -     33.165k
 Hash#dup#merge!({})      6.591k (± 5.2%) i/s -     33.150k
     {}#merge!(Hash)     17.654k (± 3.0%) i/s -     89.913k

Comparison:
     {}#merge!(Hash):    17654.0 i/s
      Hash#merge({}):     6592.7 i/s - 2.68x slower
 Hash#dup#merge!({}):     6591.2 i/s - 2.68x slower
JuanitoFatas commented 9 years ago

Thanks for the patch! I think dup#merge! you can just use Hash#merge. And it is covered by https://github.com/JuanitoFatas/fast-ruby#hashmerge-vs-hashmerge-code. Close this one.

Thanks again!

bejmuller commented 9 years ago

The reason of opening this PR was to show the difference between {}#merge!(Hash) vs Hash#merge({}) which is NOT covered anywhere in this repo.

dup.merge! was just added as a third option (which sometimes is used)

I would have appreciated a comment first, and a possibility to reply before closing it right away...

JuanitoFatas commented 9 years ago

The reason of opening this PR was to show the difference between {}#merge!(Hash) vs Hash#merge({}) which is NOT covered anywhere in this repo.

:+1:

I would have appreciated a comment first, and a possibility to reply before closing it right away...

My apologies.

bejmuller commented 9 years ago

No problem :) The description and the title I gave it are not the best, so that could have caused some confusion.

JuanitoFatas commented 9 years ago

Thank you Elod! I merged your commits in d83756f and made some minor changes :bow:

Thank you for your contribution :+1: :stars: :stars: :stars: :stars: :stars:

krzysiek1507 commented 5 years ago

{}#merge!(other) do end is the fastest only when other has only one key. Every key more means 10% slow down.


ENUM = (1..100)
ORIGINAL_HASH = { foo: { a: 1, b: { c: 3 } }, bar: {}, baz: "baz", ruby: "ruby", test: "test", yup: "yup", rails: "rails" }

def fast
  ENUM.inject([]) do |accumulator, element|
    accumulator << ({ bar: element }.merge!(ORIGINAL_HASH) { |_key, left, _right| left })
  end
end

def slow
  ENUM.inject([]) do |accumulator, element|
    accumulator << ORIGINAL_HASH.merge(bar: element)
  end
end

def slow_dup
  ENUM.inject([]) do |accumulator, element|
    accumulator << ORIGINAL_HASH.dup.merge!(bar: element)
  end
end

Benchmark.ips do |x|
  x.report("{}#merge!(Hash) do end") { fast }
  x.report("Hash#merge({})") { slow }
  x.report("Hash#dup#merge!({})") { slow_dup }
  x.compare!
end

ruby 2.6.0p0 (2018-12-25 revision 66547) [x86_64-darwin18]
Warming up --------------------------------------
{}#merge!(Hash) do end
                         1.799k i/100ms
      Hash#merge({})     2.956k i/100ms
 Hash#dup#merge!({})     1.217k i/100ms
Calculating -------------------------------------
{}#merge!(Hash) do end
                         18.216k (± 1.7%) i/s -     91.749k in   5.038319s
      Hash#merge({})     29.844k (± 2.9%) i/s -    150.756k in   5.056616s
 Hash#dup#merge!({})     12.288k (± 2.4%) i/s -     62.067k in   5.054204s

Comparison:
      Hash#merge({}):    29844.0 i/s
{}#merge!(Hash) do end:    18215.6 i/s - 1.64x  slower
 Hash#dup#merge!({}):    12287.7 i/s - 2.43x  slower