ed5d646127
Creating a copy by starting from an empty hash and inserting keys one by one is inneficient because unless the hash is very small, Ruby will have to reallocate the hash multiple times until it reaches the same saize as the original. And every time it happens, keys will have to be re-hashes. While we're at it, instead of using the generic `convert_value` we define a dedicated method which saves on needless checks. Co-Authored-By: Jean Boussier <jean.boussier@gmail.com> ```ruby require "bundler/inline" gemfile(true) do source "https://rubygems.org" gem "activesupport", path: '.' gem 'benchmark-ips' gem 'benchmark-memory' end require "active_support" require "active_support/core_ext/hash/indifferent_access" require 'benchmark/ips' module ActiveSupport class KwstannardHWIA < HashWithIndifferentAccess def to_hash Hash[self] .transform_values! { |v| convert_value(v, conversion: :to_hash) } .tap { |h| set_defaults(h) } end end class ByrootHWIA < HashWithIndifferentAccess def to_hash copy = Hash[self] copy.transform_values! { |v| convert_value_to_hash(v) } set_defaults(copy) copy end private def convert_value_to_hash(value) if value.is_a? Hash value.to_hash elsif value.is_a?(Array) value.map { |e| convert_value_to_hash(e) } else value end end end end flat = Hash[(1..9).to_a.product([1])] flat_baseline = ActiveSupport::HashWithIndifferentAccess.new(flat) flat_k = ActiveSupport::KwstannardHWIA.new(flat) flat_b = ActiveSupport::ByrootHWIA.new(flat) Benchmark.ips do |x| x.report("original") { flat_baseline.to_hash } x.report("kwstannard") { flat_k.to_hash } x.report("byroot") { flat_b.to_hash } x.compare!(order: :baseline) end long_flat = Hash[(1..100).to_a.product([1])] long_flat_baseline = ActiveSupport::HashWithIndifferentAccess.new(long_flat) long_flat_k = ActiveSupport::KwstannardHWIA.new(long_flat) long_flat_b = ActiveSupport::ByrootHWIA.new(long_flat) Benchmark.ips do |x| x.report("original") { long_flat_baseline.to_hash } x.report("kwstannard") { long_flat_k.to_hash } x.report("byroot") { long_flat_b.to_hash } x.compare!(order: :baseline) end deep_baseline = ActiveSupport::HashWithIndifferentAccess.new(flat.dup) 3.times.reduce(deep_baseline) { |deep, _| deep[:deep] = ActiveSupport::HashWithIndifferentAccess.new(flat.dup) } deep_k = ActiveSupport::KwstannardHWIA.new(flat.dup) 3.times.reduce(deep_k) { |deep, _| deep[:deep] = ActiveSupport::KwstannardHWIA.new(flat.dup) } deep_b = ActiveSupport::ByrootHWIA.new(flat.dup) 3.times.reduce(deep_b) { |deep, _| deep[:deep] = ActiveSupport::ByrootHWIA.new(flat.dup) } Benchmark.ips do |x| x.report("original") { deep_baseline.to_hash } x.report("kwstannard") { deep_k.to_hash } x.report("byroot") { deep_b.to_hash } x.compare!(order: :baseline) end ``` ``` $ ruby /tmp/to_hash.rb Fetching gem metadata from https://rubygems.org/.......... Resolving dependencies... ruby 3.3.1 (2024-04-23 revision c56cd86388) [arm64-darwin23] Warming up -------------------------------------- original 92.830k i/100ms kwstannard 125.443k i/100ms byroot 139.810k i/100ms Calculating ------------------------------------- original 968.200k (± 2.9%) i/s - 4.920M in 5.086600s kwstannard 1.268M (± 2.9%) i/s - 6.398M in 5.049545s byroot 1.397M (± 2.9%) i/s - 6.990M in 5.008404s Comparison: original: 968200.3 i/s byroot: 1397069.4 i/s - 1.44x faster kwstannard: 1268207.5 i/s - 1.31x faster ruby 3.3.1 (2024-04-23 revision c56cd86388) [arm64-darwin23] Warming up -------------------------------------- original 11.051k i/100ms kwstannard 15.865k i/100ms byroot 17.778k i/100ms Calculating ------------------------------------- original 109.059k (± 5.1%) i/s - 552.550k in 5.082820s kwstannard 157.499k (± 7.4%) i/s - 793.250k in 5.072103s byroot 177.066k (± 7.2%) i/s - 888.900k in 5.052601s Comparison: original: 109059.1 i/s byroot: 177065.9 i/s - 1.62x faster kwstannard: 157499.0 i/s - 1.44x faster ruby 3.3.1 (2024-04-23 revision c56cd86388) [arm64-darwin23] Warming up -------------------------------------- original 22.249k i/100ms kwstannard 30.132k i/100ms byroot 34.116k i/100ms Calculating ------------------------------------- original 224.127k (± 1.7%) i/s - 1.135M in 5.064253s kwstannard 295.912k (± 5.9%) i/s - 1.476M in 5.007324s byroot 343.225k (± 1.7%) i/s - 1.740M in 5.070715s Comparison: original: 224127.0 i/s byroot: 343224.6 i/s - 1.53x faster kwstannard: 295912.4 i/s - 1.32x faster ``` |
||
---|---|---|
.. | ||
bin | ||
lib | ||
test | ||
.gitignore | ||
activesupport.gemspec | ||
CHANGELOG.md | ||
MIT-LICENSE | ||
Rakefile | ||
README.rdoc |
= Active Support -- Utility classes and Ruby extensions from \Rails Active Support is a collection of utility classes and standard library extensions that were found useful for the \Rails framework. These additions reside in this package so they can be loaded as needed in Ruby projects outside of \Rails. You can read more about the extensions in the {Active Support Core Extensions}[https://guides.rubyonrails.org/active_support_core_extensions.html] guide. == Download and installation The latest version of Active Support can be installed with RubyGems: $ gem install activesupport Source code can be downloaded as part of the \Rails project on GitHub: * https://github.com/rails/rails/tree/main/activesupport == License Active Support is released under the MIT license: * https://opensource.org/licenses/MIT == Support API documentation is at: * https://api.rubyonrails.org Bug reports for the Ruby on \Rails project can be filed here: * https://github.com/rails/rails/issues Feature requests should be discussed on the rails-core mailing list here: * https://discuss.rubyonrails.org/c/rubyonrails-core