From 450d9cbfa48cc7aa9a5921a2f0b38258b1479d88 Mon Sep 17 00:00:00 2001 From: Mindaugas Pelionis Date: Thu, 14 Mar 2019 10:28:30 +0200 Subject: [PATCH 1/6] Added: Hash#values_at vs Hash#slice#values. --- README.md | 20 ++++++++++++++++++++ code/hash/values_at-vs-slice-values.rb | 23 +++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 code/hash/values_at-vs-slice-values.rb diff --git a/README.md b/README.md index b0a2e83..fecc33e 100644 --- a/README.md +++ b/README.md @@ -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 `Hash#slice#values` +[code](code/hash/values_at-vs-slice-values.rb) + +To select hash values by keys. + +``` +$ ruby -v code/hash/values_at-vs-slice-values.rb +ruby 2.6.1p33 (2019-01-30 revision 66950) [x86_64-darwin18] +Warming up -------------------------------------- + Hash#values_at 284.628k i/100ms + Hash#slice#values 230.279k i/100ms +Calculating ------------------------------------- + Hash#values_at 7.179M (± 7.4%) i/s - 35.863M in 5.034179s + Hash#slice#values 4.458M (± 4.1%) i/s - 22.337M in 5.019922s + +Comparison: + Hash#values_at : 7178610.6 i/s + Hash#slice#values: 4458017.0 i/s - 1.61x slower +``` + ### Proc & Block diff --git a/code/hash/values_at-vs-slice-values.rb b/code/hash/values_at-vs-slice-values.rb new file mode 100644 index 0000000..fbb5451 --- /dev/null +++ b/code/hash/values_at-vs-slice-values.rb @@ -0,0 +1,23 @@ +require 'benchmark/ips' + +HASH = { + one: 'foo', + two: 'bar', + three: 'baz', + four: 'qux' +} +KEYS = %i[one three] + +def fast + HASH.values_at(*KEYS) +end + +def slow + HASH.slice(*KEYS).values +end + +Benchmark.ips do |x| + x.report('Hash#values_at ') { fast } + x.report('Hash#slice#values') { slow } + x.compare! +end From 47e9ce183c9bf87342bac257169e32583cafe018 Mon Sep 17 00:00:00 2001 From: Mindaugas Pelionis Date: Thu, 21 Mar 2019 20:02:53 +0200 Subject: [PATCH 2/6] Change: add another slicing method, and add a note about assuming all keys being in the Hash. --- README.md | 25 +++++++++------- code/hash/values_at-vs-map-vs-slice-values.rb | 30 +++++++++++++++++++ code/hash/values_at-vs-slice-values.rb | 23 -------------- 3 files changed, 44 insertions(+), 34 deletions(-) create mode 100644 code/hash/values_at-vs-map-vs-slice-values.rb delete mode 100644 code/hash/values_at-vs-slice-values.rb diff --git a/README.md b/README.md index fecc33e..35013ef 100644 --- a/README.md +++ b/README.md @@ -1038,24 +1038,27 @@ 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 `Hash#slice#values` -[code](code/hash/values_at-vs-slice-values.rb) +##### `Hash#values_at` vs `Hash#slice#values` vs `Array#map { Hash#[] }` +[code](code/hash/values_at-vs-map-vs-slice-values.rb) -To select hash values by keys. +To select hash values by keys, when all the keys exist in the hash. ``` -$ ruby -v code/hash/values_at-vs-slice-values.rb -ruby 2.6.1p33 (2019-01-30 revision 66950) [x86_64-darwin18] +$ ruby -v code/hash/values_at-vs-map-vs-slice-values.rb +ruby 2.6.2p47 (2019-03-13 revision 67232) [x86_64-darwin18] Warming up -------------------------------------- - Hash#values_at 284.628k i/100ms - Hash#slice#values 230.279k i/100ms + Hash#values_at 298.254k i/100ms + Array#map 250.633k i/100ms + Hash#slice#values 243.267k i/100ms Calculating ------------------------------------- - Hash#values_at 7.179M (± 7.4%) i/s - 35.863M in 5.034179s - Hash#slice#values 4.458M (± 4.1%) i/s - 22.337M in 5.019922s + Hash#values_at 7.540M (± 1.5%) i/s - 37.878M in 5.025027s + Array#map 5.119M (± 2.3%) i/s - 25.815M in 5.045566s + Hash#slice#values 4.817M (± 1.7%) i/s - 24.083M in 5.001133s Comparison: - Hash#values_at : 7178610.6 i/s - Hash#slice#values: 4458017.0 i/s - 1.61x slower + Hash#values_at : 7539658.6 i/s + Array#map : 5119405.7 i/s - 1.47x slower + Hash#slice#values: 4817081.6 i/s - 1.57x slower ``` diff --git a/code/hash/values_at-vs-map-vs-slice-values.rb b/code/hash/values_at-vs-map-vs-slice-values.rb new file mode 100644 index 0000000..fdd1180 --- /dev/null +++ b/code/hash/values_at-vs-map-vs-slice-values.rb @@ -0,0 +1,30 @@ +require 'benchmark/ips' + +HASH = { + a: 'foo', + b: 'bar', + c: 'baz', + d: 'qux' +}.freeze + +# Only keys that exist in the hash. +KEYS = %i[a c].freeze + +def fast + HASH.values_at(*KEYS) +end + +def slow + KEYS.map { |key| HASH[key] } +end + +def slowest + HASH.slice(*KEYS).values +end + +Benchmark.ips do |x| + x.report('Hash#values_at ') { fast } + x.report('Array#map { Hash#[] }') { slow } + x.report('Hash#slice#values ') { slowest } + x.compare! +end diff --git a/code/hash/values_at-vs-slice-values.rb b/code/hash/values_at-vs-slice-values.rb deleted file mode 100644 index fbb5451..0000000 --- a/code/hash/values_at-vs-slice-values.rb +++ /dev/null @@ -1,23 +0,0 @@ -require 'benchmark/ips' - -HASH = { - one: 'foo', - two: 'bar', - three: 'baz', - four: 'qux' -} -KEYS = %i[one three] - -def fast - HASH.values_at(*KEYS) -end - -def slow - HASH.slice(*KEYS).values -end - -Benchmark.ips do |x| - x.report('Hash#values_at ') { fast } - x.report('Hash#slice#values') { slow } - x.compare! -end From 26966568d1f7b2ee53caaf151574234a45db5529 Mon Sep 17 00:00:00 2001 From: Mindaugas Pelionis Date: Thu, 21 Mar 2019 20:04:15 +0200 Subject: [PATCH 3/6] Added: other methods of slicing Hash values, when keys may not exist. --- README.md | 47 +++++++++++++++++++ ...-compact-vs-slice-values-vs-map-compact.rb | 31 ++++++++++++ code/hash/values_at-vs-map.rb | 25 ++++++++++ 3 files changed, 103 insertions(+) create mode 100644 code/hash/values_at-compact-vs-slice-values-vs-map-compact.rb create mode 100644 code/hash/values_at-vs-map.rb diff --git a/README.md b/README.md index 35013ef..4b024ac 100644 --- a/README.md +++ b/README.md @@ -1061,6 +1061,53 @@ Comparison: Hash#slice#values: 4817081.6 i/s - 1.57x 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 2.6.2p47 (2019-03-13 revision 67232) [x86_64-darwin18] +Warming up -------------------------------------- +Hash#values_at 245.809k i/100ms +Array#map { Hash#[] } 185.153k i/100ms +Calculating ------------------------------------- +Hash#values_at 5.284M (± 3.3%) i/s - 26.547M in 5.030606s +Array#map { Hash#[] } 3.104M (± 2.7%) i/s - 15.553M in 5.014067s + +Comparison: +Hash#values_at : 5283787.1 i/s +Array#map { Hash#[] }: 3104255.1 i/s - 1.70x slower +``` + +##### `Hash#slice#values` vs `Hash#values_at#compact` vs `Array#map { Hash#[] }#compact` +[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 only about the intersection (i.e. ignore the default values). + +NOTE: `#compact`-based methods only work when the default value of `Hash` is +`nil`. + +``` +$ ruby -v code/hash/values_at-compact-vs-slice-values-vs-map-compact.rb +ruby 2.6.2p47 (2019-03-13 revision 67232) [x86_64-darwin18] +Warming up -------------------------------------- +Hash#slice#values 227.519k i/100ms +Hash#values_at#compact 211.820k i/100ms +Array#map#compact 159.760k i/100ms +Calculating ------------------------------------- +Hash#slice#values 4.420M (± 1.5%) i/s - 22.297M in 5.046173s +Hash#values_at#compact 3.926M (± 1.6%) i/s - 19.699M in 5.019481s +Array#map#compact 2.508M (± 2.2%) i/s - 12.621M in 5.034508s + +Comparison: +Hash#slice#values : 4419599.9 i/s +Hash#values_at#compact: 3925677.1 i/s - 1.13x slower +Array#map#compact : 2508230.2 i/s - 1.76x slower +``` ### Proc & Block diff --git a/code/hash/values_at-compact-vs-slice-values-vs-map-compact.rb b/code/hash/values_at-compact-vs-slice-values-vs-map-compact.rb new file mode 100644 index 0000000..761387d --- /dev/null +++ b/code/hash/values_at-compact-vs-slice-values-vs-map-compact.rb @@ -0,0 +1,31 @@ +require 'benchmark/ips' + +HASH = { + a: 'foo', + b: 'bar', + c: 'baz', + d: 'qux' +}.freeze + +# Some of the keys may not exist in the hash; we don't care about the default values. +KEYS = %i[a c e f].freeze + +# NOTE: This is the only correct method, if the default value of Hash may be not nil. +def fast + HASH.slice(*KEYS).values +end + +def slow + HASH.values_at(*KEYS).compact +end + +def slowest + KEYS.map { |key| HASH[key] }.compact +end + +Benchmark.ips do |x| + x.report('Hash#slice#values ') { fast } + x.report('Hash#values_at#compact') { slow } + x.report('Array#map#compact ') { slowest } + x.compare! +end diff --git a/code/hash/values_at-vs-map.rb b/code/hash/values_at-vs-map.rb new file mode 100644 index 0000000..0e860ac --- /dev/null +++ b/code/hash/values_at-vs-map.rb @@ -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. +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 From 4b5fa8b33f6fc7f4f9439911747341f397128138 Mon Sep 17 00:00:00 2001 From: Mindaugas Pelionis Date: Thu, 2 Mar 2023 13:28:03 +0200 Subject: [PATCH 4/6] Fix: file name for `Hash#slice#values` vs `Hash#values_at#compact` vs `Array#map { Hash#[] }#compact` test. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4b024ac..44e5ff0 100644 --- a/README.md +++ b/README.md @@ -1083,7 +1083,7 @@ Array#map { Hash#[] }: 3104255.1 i/s - 1.70x slower ``` ##### `Hash#slice#values` vs `Hash#values_at#compact` vs `Array#map { Hash#[] }#compact` -[code](code/hash/values_at-vs-map.rb) +[code](code/hash/values_at-compact-vs-slice-values-vs-map-compact.rb) To select hash values by keys, when some of the keys may not exist in the hash, and you care only about the intersection (i.e. ignore the default values). From 4d329bb131e887b05f3250271429c002be6858ff Mon Sep 17 00:00:00 2001 From: Mindaugas Pelionis Date: Thu, 2 Mar 2023 14:05:05 +0200 Subject: [PATCH 5/6] Change: remove edge cases form Hash#values_at. --- README.md | 50 ------------------- ...-compact-vs-slice-values-vs-map-compact.rb | 31 ------------ code/hash/values_at-vs-map-vs-slice-values.rb | 30 ----------- 3 files changed, 111 deletions(-) delete mode 100644 code/hash/values_at-compact-vs-slice-values-vs-map-compact.rb delete mode 100644 code/hash/values_at-vs-map-vs-slice-values.rb diff --git a/README.md b/README.md index 44e5ff0..e5d2793 100644 --- a/README.md +++ b/README.md @@ -1038,29 +1038,6 @@ 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 `Hash#slice#values` vs `Array#map { Hash#[] }` -[code](code/hash/values_at-vs-map-vs-slice-values.rb) - -To select hash values by keys, when all the keys exist in the hash. - -``` -$ ruby -v code/hash/values_at-vs-map-vs-slice-values.rb -ruby 2.6.2p47 (2019-03-13 revision 67232) [x86_64-darwin18] -Warming up -------------------------------------- - Hash#values_at 298.254k i/100ms - Array#map 250.633k i/100ms - Hash#slice#values 243.267k i/100ms -Calculating ------------------------------------- - Hash#values_at 7.540M (± 1.5%) i/s - 37.878M in 5.025027s - Array#map 5.119M (± 2.3%) i/s - 25.815M in 5.045566s - Hash#slice#values 4.817M (± 1.7%) i/s - 24.083M in 5.001133s - -Comparison: - Hash#values_at : 7539658.6 i/s - Array#map : 5119405.7 i/s - 1.47x slower - Hash#slice#values: 4817081.6 i/s - 1.57x slower -``` - ##### `Hash#values_at` vs `Array#map { Hash#[] }` [code](code/hash/values_at-vs-map.rb) @@ -1082,33 +1059,6 @@ Hash#values_at : 5283787.1 i/s Array#map { Hash#[] }: 3104255.1 i/s - 1.70x slower ``` -##### `Hash#slice#values` vs `Hash#values_at#compact` vs `Array#map { Hash#[] }#compact` -[code](code/hash/values_at-compact-vs-slice-values-vs-map-compact.rb) - -To select hash values by keys, when some of the keys may not exist in the hash, -and you care only about the intersection (i.e. ignore the default values). - -NOTE: `#compact`-based methods only work when the default value of `Hash` is -`nil`. - -``` -$ ruby -v code/hash/values_at-compact-vs-slice-values-vs-map-compact.rb -ruby 2.6.2p47 (2019-03-13 revision 67232) [x86_64-darwin18] -Warming up -------------------------------------- -Hash#slice#values 227.519k i/100ms -Hash#values_at#compact 211.820k i/100ms -Array#map#compact 159.760k i/100ms -Calculating ------------------------------------- -Hash#slice#values 4.420M (± 1.5%) i/s - 22.297M in 5.046173s -Hash#values_at#compact 3.926M (± 1.6%) i/s - 19.699M in 5.019481s -Array#map#compact 2.508M (± 2.2%) i/s - 12.621M in 5.034508s - -Comparison: -Hash#slice#values : 4419599.9 i/s -Hash#values_at#compact: 3925677.1 i/s - 1.13x slower -Array#map#compact : 2508230.2 i/s - 1.76x slower -``` - ### Proc & Block ##### Block vs `Symbol#to_proc` [code](code/proc-and-block/block-vs-to_proc.rb) diff --git a/code/hash/values_at-compact-vs-slice-values-vs-map-compact.rb b/code/hash/values_at-compact-vs-slice-values-vs-map-compact.rb deleted file mode 100644 index 761387d..0000000 --- a/code/hash/values_at-compact-vs-slice-values-vs-map-compact.rb +++ /dev/null @@ -1,31 +0,0 @@ -require 'benchmark/ips' - -HASH = { - a: 'foo', - b: 'bar', - c: 'baz', - d: 'qux' -}.freeze - -# Some of the keys may not exist in the hash; we don't care about the default values. -KEYS = %i[a c e f].freeze - -# NOTE: This is the only correct method, if the default value of Hash may be not nil. -def fast - HASH.slice(*KEYS).values -end - -def slow - HASH.values_at(*KEYS).compact -end - -def slowest - KEYS.map { |key| HASH[key] }.compact -end - -Benchmark.ips do |x| - x.report('Hash#slice#values ') { fast } - x.report('Hash#values_at#compact') { slow } - x.report('Array#map#compact ') { slowest } - x.compare! -end diff --git a/code/hash/values_at-vs-map-vs-slice-values.rb b/code/hash/values_at-vs-map-vs-slice-values.rb deleted file mode 100644 index fdd1180..0000000 --- a/code/hash/values_at-vs-map-vs-slice-values.rb +++ /dev/null @@ -1,30 +0,0 @@ -require 'benchmark/ips' - -HASH = { - a: 'foo', - b: 'bar', - c: 'baz', - d: 'qux' -}.freeze - -# Only keys that exist in the hash. -KEYS = %i[a c].freeze - -def fast - HASH.values_at(*KEYS) -end - -def slow - KEYS.map { |key| HASH[key] } -end - -def slowest - HASH.slice(*KEYS).values -end - -Benchmark.ips do |x| - x.report('Hash#values_at ') { fast } - x.report('Array#map { Hash#[] }') { slow } - x.report('Hash#slice#values ') { slowest } - x.compare! -end From 9b9b2741fd4215c784dc95c6577f8f73b3dc1771 Mon Sep 17 00:00:00 2001 From: Mindaugas Pelionis Date: Thu, 2 Mar 2023 14:05:37 +0200 Subject: [PATCH 6/6] Change: update benchmarks for code/hash/values_at-vs-map.rb --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index e5d2793..d7bcbe1 100644 --- a/README.md +++ b/README.md @@ -1046,17 +1046,17 @@ and you care about the default values. ``` $ ruby -v code/hash/values_at-vs-map.rb -ruby 2.6.2p47 (2019-03-13 revision 67232) [x86_64-darwin18] +ruby 3.2.1 (2023-02-08 revision 31819e82c8) [x86_64-darwin22] Warming up -------------------------------------- -Hash#values_at 245.809k i/100ms -Array#map { Hash#[] } 185.153k i/100ms +Hash#values_at 503.783k i/100ms +Array#map { Hash#[] } 279.576k i/100ms Calculating ------------------------------------- -Hash#values_at 5.284M (± 3.3%) i/s - 26.547M in 5.030606s -Array#map { Hash#[] } 3.104M (± 2.7%) i/s - 15.553M in 5.014067s +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 : 5283787.1 i/s -Array#map { Hash#[] }: 3104255.1 i/s - 1.70x slower +Hash#values_at : 4900567.9 i/s +Array#map { Hash#[] }: 2800981.4 i/s - 1.75x slower ``` ### Proc & Block