Commit 70b73351 authored by Matthias Käppler's avatar Matthias Käppler

Merge branch 'optimise-strong-memoize' into 'master'

Reduce memory allocations for `StrongMemoize`

See merge request gitlab-org/gitlab!81920
parents 27ca9b47 31708fd9
...@@ -420,6 +420,7 @@ group :test do ...@@ -420,6 +420,7 @@ group :test do
gem 'fuubar', '~> 2.2.0' gem 'fuubar', '~> 2.2.0'
gem 'rspec-retry', '~> 0.6.1' gem 'rspec-retry', '~> 0.6.1'
gem 'rspec_profiling', '~> 0.0.6' gem 'rspec_profiling', '~> 0.0.6'
gem 'rspec-benchmark', '~> 0.6.0'
gem 'rspec-parameterized', require: false gem 'rspec-parameterized', require: false
gem 'capybara', '~> 3.35.3' gem 'capybara', '~> 3.35.3'
......
...@@ -139,8 +139,11 @@ GEM ...@@ -139,8 +139,11 @@ GEM
bcrypt (3.1.16) bcrypt (3.1.16)
benchmark (0.1.1) benchmark (0.1.1)
benchmark-ips (2.3.0) benchmark-ips (2.3.0)
benchmark-malloc (0.2.0)
benchmark-memory (0.1.2) benchmark-memory (0.1.2)
memory_profiler (~> 0.9) memory_profiler (~> 0.9)
benchmark-perf (0.6.0)
benchmark-trend (0.4.0)
better_errors (2.9.1) better_errors (2.9.1)
coderay (>= 1.0.0) coderay (>= 1.0.0)
erubi (>= 1.0.0) erubi (>= 1.0.0)
...@@ -1062,6 +1065,11 @@ GEM ...@@ -1062,6 +1065,11 @@ GEM
rspec-core (~> 3.10.0) rspec-core (~> 3.10.0)
rspec-expectations (~> 3.10.0) rspec-expectations (~> 3.10.0)
rspec-mocks (~> 3.10.0) rspec-mocks (~> 3.10.0)
rspec-benchmark (0.6.0)
benchmark-malloc (~> 0.2)
benchmark-perf (~> 0.6)
benchmark-trend (~> 0.4)
rspec (>= 3.0)
rspec-core (3.10.1) rspec-core (3.10.1)
rspec-support (~> 3.10.0) rspec-support (~> 3.10.0)
rspec-expectations (3.10.1) rspec-expectations (3.10.1)
...@@ -1600,6 +1608,7 @@ DEPENDENCIES ...@@ -1600,6 +1608,7 @@ DEPENDENCIES
rexml (~> 3.2.5) rexml (~> 3.2.5)
rouge (~> 3.27.0) rouge (~> 3.27.0)
rqrcode-rails3 (~> 0.1.7) rqrcode-rails3 (~> 0.1.7)
rspec-benchmark (~> 0.6.0)
rspec-parameterized rspec-parameterized
rspec-rails (~> 5.0.1) rspec-rails (~> 5.0.1)
rspec-retry (~> 0.6.1) rspec-retry (~> 0.6.1)
......
...@@ -22,10 +22,12 @@ module Gitlab ...@@ -22,10 +22,12 @@ module Gitlab
# end # end
# #
def strong_memoize(name) def strong_memoize(name)
if strong_memoized?(name) key = ivar(name)
instance_variable_get(ivar(name))
if instance_variable_defined?(key)
instance_variable_get(key)
else else
instance_variable_set(ivar(name), yield) instance_variable_set(key, yield)
end end
end end
...@@ -34,13 +36,23 @@ module Gitlab ...@@ -34,13 +36,23 @@ module Gitlab
end end
def clear_memoization(name) def clear_memoization(name)
remove_instance_variable(ivar(name)) if instance_variable_defined?(ivar(name)) key = ivar(name)
remove_instance_variable(key) if instance_variable_defined?(key)
end end
private private
# Convert `"name"`/`:name` into `:@name`
#
# Depending on a type ensure that there's a single memory allocation
def ivar(name) def ivar(name)
"@#{name}" if name.is_a?(Symbol)
name.to_s.prepend("@").to_sym
elsif name.is_a?(String)
:"@#{name}"
else
raise ArgumentError, "Invalid type of '#{name}'"
end
end end
end end
end end
......
...@@ -48,6 +48,36 @@ RSpec.describe Gitlab::Utils::StrongMemoize do ...@@ -48,6 +48,36 @@ RSpec.describe Gitlab::Utils::StrongMemoize do
let(:value) { value } let(:value) { value }
it_behaves_like 'caching the value' it_behaves_like 'caching the value'
it 'raises exception for invalid key' do
expect { object.strong_memoize(10) { 20 } }.to raise_error /Invalid type of '10'/
end
end
end
context "memory allocation", type: :benchmark do
let(:value) { 'aaa' }
before do
object.method_name # warmup
end
[:method_name, "method_name"].each do |argument|
context "for #{argument.class}" do
it 'does allocate exactly one string when fetching value' do
expect do
object.strong_memoize(argument) { 10 }
end.to perform_allocation(1)
end
it 'does allocate exactly one string when storing value' do
object.clear_memoization(:method_name) # clear to force set
expect do
object.strong_memoize(argument) { 10 }
end.to perform_allocation(1)
end
end
end end
end end
end end
......
...@@ -199,6 +199,7 @@ RSpec.configure do |config| ...@@ -199,6 +199,7 @@ RSpec.configure do |config|
config.include SidekiqMiddleware config.include SidekiqMiddleware
config.include StubActionCableConnection, type: :channel config.include StubActionCableConnection, type: :channel
config.include StubSpamServices config.include StubSpamServices
config.include RSpec::Benchmark::Matchers, type: :benchmark
include StubFeatureFlags include StubFeatureFlags
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment