Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Hash#values_at vs Array#map { Hash#[] } comparison #174

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1038,6 +1038,26 @@ Array#each_w/_object: 1352851.8 i/s - 1.88x slower
Hash#select-include : 760944.2 i/s - 3.34x slower
```

##### `Hash#values_at` vs `Array#map { Hash#[] }`
[code](code/hash/values_at-vs-map.rb)

To select hash values by keys, when some of the keys may not exist in the hash,
and you care about the default values.

```
$ ruby -v code/hash/values_at-vs-map.rb
ruby 3.2.1 (2023-02-08 revision 31819e82c8) [x86_64-darwin22]
Warming up --------------------------------------
Hash#values_at 503.783k i/100ms
Array#map { Hash#[] } 279.576k i/100ms
Calculating -------------------------------------
Hash#values_at 4.901M (± 4.1%) i/s - 24.685M in 5.046090s
Array#map { Hash#[] } 2.801M (± 3.1%) i/s - 14.258M in 5.095692s

Comparison:
Hash#values_at : 4900567.9 i/s
Array#map { Hash#[] }: 2800981.4 i/s - 1.75x slower
```

### Proc & Block

Expand Down
25 changes: 25 additions & 0 deletions code/hash/values_at-vs-map.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
require 'benchmark/ips'

HASH = {
a: 'foo',
b: 'bar',
c: 'baz',
d: 'qux'
}.freeze

# Some of the keys may not exist in the hash; we want to keep the default values.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For existing keys, the benchmark does not change much.

# frozen_string_literal: true

require 'benchmark/ips'

HASH = {
  a: 'foo',
  b: 'bar',
  c: 'baz',
  d: 'qux'
}.freeze

# the keys exist in the hash
KEYS = %i[a b c d].freeze

def fastest
  HASH.values_at(*KEYS)
end

def slow
  HASH.fetch_values(*KEYS)
end

def slowest
  KEYS.map { |key| HASH[key] }
end

Benchmark.ips do |x|
  x.report('Hash#values_at       ') { fastest }
  x.report('Hash#fetch_values    ') { slow }
  x.report('Array#map { Hash#[] }') { slowest }
  x.compare!
end
ydakuka@yauhenid:~/ruby-docker-app$ docker run ruby-app  
Warming up --------------------------------------
Hash#values_at       
                       405.669k i/100ms
Hash#fetch_values    
                       354.263k i/100ms
Array#map { Hash#[] }
                       221.960k i/100ms
Calculating -------------------------------------
Hash#values_at       
                          4.191M (± 4.7%) i/s -     21.095M in   5.045130s
Hash#fetch_values    
                          3.895M (± 6.8%) i/s -     19.484M in   5.031663s
Array#map { Hash#[] }
                          2.429M (± 6.1%) i/s -     12.208M in   5.048470s

Comparison:
Hash#values_at       :  4190993.6 i/s
Hash#fetch_values    :  3894866.5 i/s - same-ish: difference falls within error
Array#map { Hash#[] }:  2429428.6 i/s - 1.73x  slower

KEYS = %i[a c e f].freeze

def fast
HASH.values_at(*KEYS)
end

def slow
KEYS.map { |key| HASH[key] }
end

Benchmark.ips do |x|
x.report('Hash#values_at ') { fast }
x.report('Array#map { Hash#[] }') { slow }
x.compare!
end