Optimize Hash#stringify_keys

Using Symbol#name allows to hit two birds with one stone.

First it will return a pre-existing string, so will save
one allocation per key.

Second, that string will be already interned, so it will
save the internal `Hash` implementation the work of looking
up the interned strings table to deduplicate the key.

```
ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [x86_64-darwin21]
Warming up --------------------------------------
                to_s    17.768k i/100ms
                cond    23.703k i/100ms
Calculating -------------------------------------
                to_s    169.830k (±10.4%) i/s -    852.864k in   5.088377s
                cond    236.803k (± 7.9%) i/s -      1.185M in   5.040945s

Comparison:
                to_s:   169830.3 i/s
                cond:   236803.4 i/s - 1.39x  faster
```

```ruby
require 'bundler/inline'

gemfile do
  source 'https://rubygems.org'
  gem 'benchmark-ips', require: false
end

HASH = {
  first_name: nil,
  last_name: nil,
  country: nil,
  profession: nil,
  language: nil,
  hobby: nil,
  pet: nil,
  longer_name: nil,
  occupation: nil,
  mailing_address: nil,
}.freeze

require 'benchmark/ips'

Benchmark.ips do |x|
  x.report("to_s") { HASH.transform_keys(&:to_s) }
  x.report("cond") { HASH.transform_keys { |k| Symbol === k ? k.name : k.to_s } }
  x.compare!(order: :baseline)
end
```
This commit is contained in:
Jean Boussier 2024-01-08 18:43:37 +01:00
parent 0b04c15147
commit 8c7e69b79b
3 changed files with 12 additions and 6 deletions

View File

@ -8,13 +8,13 @@ class Hash
# hash.stringify_keys
# # => {"name"=>"Rob", "age"=>"28"}
def stringify_keys
transform_keys(&:to_s)
transform_keys { |k| Symbol === k ? k.name : k.to_s }
end
# Destructively converts all keys to strings. Same as
# +stringify_keys+, but modifies +self+.
def stringify_keys!
transform_keys!(&:to_s)
transform_keys! { |k| Symbol === k ? k.name : k.to_s }
end
# Returns a new hash with all keys converted to symbols, as long as
@ -82,14 +82,14 @@ class Hash
# hash.deep_stringify_keys
# # => {"person"=>{"name"=>"Rob", "age"=>"28"}}
def deep_stringify_keys
deep_transform_keys(&:to_s)
deep_transform_keys { |k| Symbol === k ? k.name : k.to_s }
end
# Destructively converts all keys to strings.
# This includes the keys from the root hash and from all
# nested hashes and arrays.
def deep_stringify_keys!
deep_transform_keys!(&:to_s)
deep_transform_keys! { |k| Symbol === k ? k.name : k.to_s }
end
# Returns a new hash with all keys converted to symbols, as long as

View File

@ -393,7 +393,7 @@ module ActiveSupport
private
def convert_key(key)
key.kind_of?(Symbol) ? key.name : key
Symbol === key ? key.name : key
end
def convert_value(value, conversion: nil)

View File

@ -76,7 +76,13 @@ module ActiveSupport
when Hash
result = {}
value.each do |k, v|
k = k.to_s unless String === k
unless String === k
k = if Symbol === k
k.name
else
k.to_s
end
end
result[k] = jsonify(v)
end
result