Memoize config hash on finalize (#151)

Freezing the hash is beneficial at this point because it saves repeated expensive
computation if that hash is to be used later in performance-sensitive situations, such as
when serving as a cache key or similar.
This commit is contained in:
Tim Riley 2022-10-13 07:04:57 +11:00 committed by GitHub
parent 4f3039de71
commit 592fb1ca9f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 43 additions and 0 deletions

View File

@ -19,4 +19,5 @@ end
group :tools do
gem "hotch", platform: :mri
gem "pry-byebug", platform: :mri
gem "rspec-benchmark"
end

View File

@ -146,6 +146,16 @@ module Dry
values.to_h { |key, value| [key, value.is_a?(self.class) ? value.to_h : value] }
end
# @api private
alias_method :_dry_equalizer_hash, :hash
# @api public
def hash
return @__hash__ if instance_variable_defined?(:@__hash__)
_dry_equalizer_hash
end
# @api private
def finalize!(freeze_values: false)
values.each_value do |value|
@ -156,6 +166,13 @@ module Dry
end
end
# Memoize the hash for the object when finalizing (regardless of whether values themselves
# are to be frozen; the intention of finalization is that no further changes should be
# made). The benefit of freezing the hash at this point is that it saves repeated expensive
# computation (through Dry::Equalizer's hash implementation) if that hash is to be used
# later in performance-sensitive situations, such as when serving as a cache key or similar.
@__hash__ = _dry_equalizer_hash unless frozen?
freeze
end

View File

@ -239,6 +239,28 @@ RSpec.describe Dry::Configurable::Config do
end
end
describe "#hash" do
it "returns the integer hash value for the convig based on its values" do
klass.setting :db
expect(klass.config.hash).to be_an_instance_of(Integer)
expect { klass.config.db = "sqlite" }.to change { klass.config.hash }
end
it "is memoized when the config is finalized", :performance do
klass.setting :a
klass.setting :b
klass.setting :c
klass.setting :d
klass.setting :e
finalized_config = klass.config.dup.finalize!
expect(finalized_config.hash).to eq klass.config.hash
expect { finalized_config.hash }.to perform_faster_than { klass.config.hash }.at_least(50).times
end
end
describe "#method_missing" do
it "provides access to reader methods" do
klass.setting :hello

View File

@ -2,6 +2,7 @@
require_relative "support/coverage"
require "pathname"
require "rspec-benchmark"
SPEC_ROOT = Pathname(__FILE__).dirname
@ -22,6 +23,8 @@ RSpec.configure do |config|
config.disable_monkey_patching!
config.filter_run_when_matching :focus
config.include RSpec::Benchmark::Matchers
config.around do |example|
module Test
end