From 09aca82c9a3a4563edfda2f5cb6186ae910d8b79 Mon Sep 17 00:00:00 2001 From: Nick Gorbikoff Date: Sat, 7 Oct 2017 18:49:51 -0500 Subject: [PATCH 1/4] Add benchmark to test :key vs 'key' when generating a hash --- CONTRIBUTING.md | 2 +- README.md | 49 +++++++++++++++++++ ...ing-keys-vs-symbol-keys-with-1000-pairs.rb | 22 +++++++++ code/hash/string-keys-vs-symbol-keys.rb | 40 +++++++++++++++ 4 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 code/hash/string-keys-vs-symbol-keys-with-1000-pairs.rb create mode 100644 code/hash/string-keys-vs-symbol-keys.rb diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ba4a1f0..1d69890 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ If you find any typos, errors, or have an better example. Just raise a new issue <3 -These idioms list here are trying to satisfiy following goals: +These idioms list here are trying to satisfy following goals: [![GOALS](/images/Goals.png)](https://speakerdeck.com/sferik/writing-fast-ruby?slide=11) diff --git a/README.md b/README.md index eb924d5..34c7682 100644 --- a/README.md +++ b/README.md @@ -622,6 +622,55 @@ Comparison: Hash#fetch, string: 3981166.5 i/s - 1.89x slower ``` +##### `Hash['key']` vs `Hash[:key]` [code](code/hash/string-keys-vs-symbol-keys.rb) + +In ruby 2.3.5 *generating* Hash with Symbol keys are a bit more performant than String keys. But the difference is insignificant for Hash with 1 pair of key/value. +In ruby 2.4.2 Symbol keys are about 15%-19% faster. Using String keys can come with a small penalty, as seen below. + +``` +$ruby -v code/hash/string-keys-vs-symbol-keys.rb +ruby 2.4.2p198 (2017-09-14 revision 59899) [x86_64-darwin16] +Generating simple Hashes with just 1 key/value using different types of keys +Generating using implicit form +Warming up -------------------------------------- + {symbol: 42} 112.603k i/100ms + {:symbol => 42} 113.512k i/100ms + {'sym_str': 42} 111.994k i/100ms + {"string" => 42} 109.474k i/100ms + {'string' => 42} 110.688k i/100ms +Calculating ------------------------------------- + {symbol: 42} 1.731M (± 2.0%) i/s - 8.670M in 5.010332s + {:symbol => 42} 1.714M (± 2.0%) i/s - 8.627M in 5.034970s + {'sym_str': 42} 1.711M (± 3.5%) i/s - 8.624M in 5.046647s + {"string" => 42} 1.508M (± 9.3%) i/s - 7.554M in 5.064608s + {'string' => 42} 1.453M (± 5.7%) i/s - 7.305M in 5.045950s + +Comparison: + {symbol: 42}: 1731221.3 i/s + {:symbol => 42}: 1714113.4 i/s - same-ish: difference falls within error + {'sym_str': 42}: 1711084.8 i/s - same-ish: difference falls within error + {"string" => 42}: 1508413.1 i/s - 1.15x slower + {'string' => 42}: 1452896.9 i/s - 1.19x slower +``` + +However if you need to generate large Hash with 1000 key/value pairs the difference becomes more obvious. Hash with symbol keys is about 35% faster (in ruby 2.3.5 it is about 17% faster). [code](code/hash/string-keys-vs-symbol-keys-with-1000-pairs.rb) + +``` +$ruby -v code/hash/string-keys-vs-symbol-keys-with-1000-pairs.rb +ruby 2.4.2p198 (2017-09-14 revision 59899) [x86_64-darwin16] +Warming up -------------------------------------- + Symbol Keys 340.000 i/100ms + String Keys 248.000 i/100ms +Calculating ------------------------------------- + Symbol Keys 3.385k (± 5.2%) i/s - 17.000k in 5.036258s + String Keys 2.486k (± 3.9%) i/s - 12.648k in 5.095433s + +Comparison: + Symbol Keys: 3385.2 i/s + String Keys: 2485.9 i/s - 1.36x slower +``` + + ##### `Hash#dig` vs `Hash#[]` vs `Hash#fetch` [code](code/hash/dig-vs-[]-vs-fetch.rb) [Ruby 2.3 introduced `Hash#dig`](http://ruby-doc.org/core-2.3.0/Hash.html#method-i-dig) which is a readable diff --git a/code/hash/string-keys-vs-symbol-keys-with-1000-pairs.rb b/code/hash/string-keys-vs-symbol-keys-with-1000-pairs.rb new file mode 100644 index 0000000..33771bf --- /dev/null +++ b/code/hash/string-keys-vs-symbol-keys-with-1000-pairs.rb @@ -0,0 +1,22 @@ +require "benchmark/ips" + + +STRING_KEYS = (1..1000).map{|x| "key_#{x}"}.shuffle +SYMBOL_KEYS = STRING_KEYS.map(&:to_sym) + +def symbol_hash + SYMBOL_KEYS.collect { |k| [ k, rand(1..100)]}.to_h +end + +def string_hash + STRING_KEYS.collect { |k| [ k, rand(1..100)]}.to_h +end + + +Benchmark.ips do |x| + + x.report("Symbol Keys") { symbol_hash } + x.report("String Keys") { string_hash } + + x.compare! +end diff --git a/code/hash/string-keys-vs-symbol-keys.rb b/code/hash/string-keys-vs-symbol-keys.rb new file mode 100644 index 0000000..f631820 --- /dev/null +++ b/code/hash/string-keys-vs-symbol-keys.rb @@ -0,0 +1,40 @@ +require "benchmark/ips" + +# While using symbols on small hashes IS faster, the difference is very insignificant even on 10_000_000 iterations +# From fastest to slowest. +# +def symbol_key + {symbol: 42} +end + +def symbol_key_arrow + {:symbol => 42} +end + +def symbol_key_in_string_form + {'sym_str': 42} +end + +def string_key_arrow_double_quotes + {"string" => 42} +end + +def string_key_arrow_single_quotes + {'string' => 42} +end + + + +Benchmark.ips do |x| + + puts "Generating simple Hashes with just 1 key/value using different types of keys" + puts "Generating using implicit form" + + x.report("{symbol: 42}") {symbol_key} + x.report("{:symbol => 42}") {symbol_key_arrow} + x.report("{'sym_str': 42}") {symbol_key_in_string_form} + x.report("{\"string\" => 42}") {string_key_arrow_double_quotes} + x.report("{'string' => 42}") {string_key_arrow_double_quotes} + + x.compare! +end From 8079c9cd9b49653e7d9daeb5ebf1d791489a59c4 Mon Sep 17 00:00:00 2001 From: Nick Gorbikoff Date: Sat, 7 Oct 2017 19:11:20 -0500 Subject: [PATCH 2/4] Add benchmark to test reading / writing for 1000 k/v pairs for :key vs 'key' when generating a hash --- README.md | 27 ++++++++++++++----- ...ing-keys-vs-symbol-keys-with-1000-pairs.rb | 20 ++++++++++++++ 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 34c7682..6ca17f1 100644 --- a/README.md +++ b/README.md @@ -653,21 +653,34 @@ Comparison: {'string' => 42}: 1452896.9 i/s - 1.19x slower ``` -However if you need to generate large Hash with 1000 key/value pairs the difference becomes more obvious. Hash with symbol keys is about 35% faster (in ruby 2.3.5 it is about 17% faster). [code](code/hash/string-keys-vs-symbol-keys-with-1000-pairs.rb) +However if you need to generate large Hash with 1000 key/value pairs the difference becomes more obvious. Hash with symbol keys is about 35%-37% faster (in ruby 2.3.5 it is about 17% faster). Reading Hash with Symbol keys is also faster by about 23%. The bigger is your Hash the more apparent the difference. Depending on how values in pairs are generated, performance could be even better, upto 2x times. [code](code/hash/string-keys-vs-symbol-keys-with-1000-pairs.rb) ``` $ruby -v code/hash/string-keys-vs-symbol-keys-with-1000-pairs.rb ruby 2.4.2p198 (2017-09-14 revision 59899) [x86_64-darwin16] +Creating large Hash Warming up -------------------------------------- - Symbol Keys 340.000 i/100ms - String Keys 248.000 i/100ms + Symbol Keys 338.000 i/100ms + String Keys 253.000 i/100ms Calculating ------------------------------------- - Symbol Keys 3.385k (± 5.2%) i/s - 17.000k in 5.036258s - String Keys 2.486k (± 3.9%) i/s - 12.648k in 5.095433s + Symbol Keys 3.420k (± 4.4%) i/s - 17.238k in 5.050034s + String Keys 2.502k (± 4.7%) i/s - 12.650k in 5.066595s Comparison: - Symbol Keys: 3385.2 i/s - String Keys: 2485.9 i/s - 1.36x slower + Symbol Keys: 3420.2 i/s + String Keys: 2502.4 i/s - 1.37x slower + +Reading large Hash +Warming up -------------------------------------- + Symbol Keys 219.892k i/100ms + String Keys 197.308k i/100ms +Calculating ------------------------------------- + Symbol Keys 5.469M (± 6.1%) i/s - 27.267M in 5.004309s + String Keys 4.447M (± 5.6%) i/s - 22.296M in 5.029068s + +Comparison: + Symbol Keys: 5469417.4 i/s + String Keys: 4447190.1 i/s - 1.23x slower ``` diff --git a/code/hash/string-keys-vs-symbol-keys-with-1000-pairs.rb b/code/hash/string-keys-vs-symbol-keys-with-1000-pairs.rb index 33771bf..c73e655 100644 --- a/code/hash/string-keys-vs-symbol-keys-with-1000-pairs.rb +++ b/code/hash/string-keys-vs-symbol-keys-with-1000-pairs.rb @@ -4,6 +4,7 @@ STRING_KEYS = (1..1000).map{|x| "key_#{x}"}.shuffle SYMBOL_KEYS = STRING_KEYS.map(&:to_sym) +# If we use static values for Hash, speed improves even more. def symbol_hash SYMBOL_KEYS.collect { |k| [ k, rand(1..100)]}.to_h end @@ -12,11 +13,30 @@ def string_hash STRING_KEYS.collect { |k| [ k, rand(1..100)]}.to_h end +SYMBOL_HASH = symbol_hash +STRING_HASH = string_hash + + +def reading_symbol_hash + SYMBOL_HASH[SYMBOL_KEYS.sample] +end + +def reading_string_hash + STRING_HASH[STRING_KEYS.sample] +end Benchmark.ips do |x| + puts "Creating large Hash" x.report("Symbol Keys") { symbol_hash } x.report("String Keys") { string_hash } x.compare! end + +Benchmark.ips do |x| + puts "Reading large Hash" + x.report("Symbol Keys") { reading_symbol_hash } + x.report("String Keys") { reading_string_hash } + x.compare! +end From d42cf992fac0ec70fb433ec7e99afc98828d1399 Mon Sep 17 00:00:00 2001 From: Nick Gorbikoff Date: Mon, 9 Oct 2017 10:29:27 -0500 Subject: [PATCH 3/4] Adding frozen string keys example to the Hash[:key] vs Hash['key'] benchmark, per http://blog.arkency.com/could-we-drop-symbols-from-ruby/ --- README.md | 34 +++++++++++-------- ...ing-keys-vs-symbol-keys-with-1000-pairs.rb | 15 ++++++++ 2 files changed, 35 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 6ca17f1..c5ba85b 100644 --- a/README.md +++ b/README.md @@ -653,34 +653,40 @@ Comparison: {'string' => 42}: 1452896.9 i/s - 1.19x slower ``` -However if you need to generate large Hash with 1000 key/value pairs the difference becomes more obvious. Hash with symbol keys is about 35%-37% faster (in ruby 2.3.5 it is about 17% faster). Reading Hash with Symbol keys is also faster by about 23%. The bigger is your Hash the more apparent the difference. Depending on how values in pairs are generated, performance could be even better, upto 2x times. [code](code/hash/string-keys-vs-symbol-keys-with-1000-pairs.rb) +However if you need to generate large Hash with 1000 key/value pairs the difference becomes more obvious. Hash with symbol keys is about 32%-37% faster (in ruby 2.3.5 it is about 17% faster). Reading Hash with Symbol keys is also faster by about 23%. The bigger is your Hash the more apparent the difference. Depending on how values in pairs are generated, performance could be even better, upto 2x times. [code](code/hash/string-keys-vs-symbol-keys-with-1000-pairs.rb). However using 'string_key'.freeze could result in comparable performance, on a large Hash. ``` -$ruby -v code/hash/string-keys-vs-symbol-keys-with-1000-pairs.rb +ruby -v code/hash/string-keys-vs-symbol-keys-with-1000-pairs.rb Mon Oct 9 10:14:49 2017 ruby 2.4.2p198 (2017-09-14 revision 59899) [x86_64-darwin16] Creating large Hash Warming up -------------------------------------- - Symbol Keys 338.000 i/100ms - String Keys 253.000 i/100ms + Symbol Keys 326.000 i/100ms + String Keys 241.000 i/100ms + Frozen Keys 293.000 i/100ms Calculating ------------------------------------- - Symbol Keys 3.420k (± 4.4%) i/s - 17.238k in 5.050034s - String Keys 2.502k (± 4.7%) i/s - 12.650k in 5.066595s + Symbol Keys 3.262k (± 3.7%) i/s - 16.300k in 5.003892s + String Keys 2.477k (± 4.4%) i/s - 12.532k in 5.069933s + Frozen Keys 3.023k (± 4.6%) i/s - 15.236k in 5.050441s Comparison: - Symbol Keys: 3420.2 i/s - String Keys: 2502.4 i/s - 1.37x slower + Symbol Keys: 3262.0 i/s + Frozen Keys: 3023.2 i/s - same-ish: difference falls within error + String Keys: 2476.7 i/s - 1.32x slower Reading large Hash Warming up -------------------------------------- - Symbol Keys 219.892k i/100ms - String Keys 197.308k i/100ms + Symbol Keys 212.401k i/100ms + String Keys 190.175k i/100ms + Frozen Keys 205.491k i/100ms Calculating ------------------------------------- - Symbol Keys 5.469M (± 6.1%) i/s - 27.267M in 5.004309s - String Keys 4.447M (± 5.6%) i/s - 22.296M in 5.029068s + Symbol Keys 5.281M (± 8.1%) i/s - 26.338M in 5.022333s + String Keys 4.276M (± 6.2%) i/s - 21.300M in 5.000911s + Frozen Keys 4.791M (± 6.0%) i/s - 24.042M in 5.036991s Comparison: - Symbol Keys: 5469417.4 i/s - String Keys: 4447190.1 i/s - 1.23x slower + Symbol Keys: 5280882.7 i/s + Frozen Keys: 4791128.0 i/s - same-ish: difference falls within error + String Keys: 4275730.5 i/s - 1.24x slower ``` diff --git a/code/hash/string-keys-vs-symbol-keys-with-1000-pairs.rb b/code/hash/string-keys-vs-symbol-keys-with-1000-pairs.rb index c73e655..2c720e7 100644 --- a/code/hash/string-keys-vs-symbol-keys-with-1000-pairs.rb +++ b/code/hash/string-keys-vs-symbol-keys-with-1000-pairs.rb @@ -2,6 +2,7 @@ STRING_KEYS = (1..1000).map{|x| "key_#{x}"}.shuffle +FROZEN_KEYS = STRING_KEYS.map{|x| "fr_#{x}".freeze} SYMBOL_KEYS = STRING_KEYS.map(&:to_sym) # If we use static values for Hash, speed improves even more. @@ -13,8 +14,16 @@ def string_hash STRING_KEYS.collect { |k| [ k, rand(1..100)]}.to_h end +# See this article for the discussion of using frozen strings instead of symbols +# http://blog.arkency.com/could-we-drop-symbols-from-ruby/ +def frozen_hash + FROZEN_KEYS.collect { |k| [ k, rand(1..100)]}.to_h +end + + SYMBOL_HASH = symbol_hash STRING_HASH = string_hash +FROZEN_HASH = frozen_hash def reading_symbol_hash @@ -25,11 +34,16 @@ def reading_string_hash STRING_HASH[STRING_KEYS.sample] end +def reading_frozen_hash + FROZEN_HASH[FROZEN_KEYS.sample] +end + Benchmark.ips do |x| puts "Creating large Hash" x.report("Symbol Keys") { symbol_hash } x.report("String Keys") { string_hash } + x.report("Frozen Keys") { frozen_hash } x.compare! end @@ -38,5 +52,6 @@ def reading_string_hash puts "Reading large Hash" x.report("Symbol Keys") { reading_symbol_hash } x.report("String Keys") { reading_string_hash } + x.report("Frozen Keys") { reading_frozen_hash } x.compare! end From edacb294d8d27bd3347924fe7e5e634f85839ad9 Mon Sep 17 00:00:00 2001 From: Nick Gorbikoff <{ID}+{username}@users.noreply.github.com> Date: Tue, 8 Oct 2024 11:03:10 -0500 Subject: [PATCH 4/4] Improving benchmark --- code/hash/.tool-versions | 1 + code/hash/string-keys-vs-symbol-keys-with-1000-pairs.rb | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 code/hash/.tool-versions diff --git a/code/hash/.tool-versions b/code/hash/.tool-versions new file mode 100644 index 0000000..a9e31a4 --- /dev/null +++ b/code/hash/.tool-versions @@ -0,0 +1 @@ +ruby 2.7.1 diff --git a/code/hash/string-keys-vs-symbol-keys-with-1000-pairs.rb b/code/hash/string-keys-vs-symbol-keys-with-1000-pairs.rb index 2c720e7..5873c04 100644 --- a/code/hash/string-keys-vs-symbol-keys-with-1000-pairs.rb +++ b/code/hash/string-keys-vs-symbol-keys-with-1000-pairs.rb @@ -1,9 +1,9 @@ require "benchmark/ips" -STRING_KEYS = (1..1000).map{|x| "key_#{x}"}.shuffle -FROZEN_KEYS = STRING_KEYS.map{|x| "fr_#{x}".freeze} -SYMBOL_KEYS = STRING_KEYS.map(&:to_sym) +STRING_KEYS = (1001..2000).map{|x| "key_#{x}"}.shuffle +FROZEN_KEYS = (2001..3000).map{|x| "key_#{x}".freeze}.shuffle +SYMBOL_KEYS = (3001..4000).map{|x| "key_#{x}".to_sym}.shuffle # If we use static values for Hash, speed improves even more. def symbol_hash