Reduce memory allocations for `StrongMemoize`
What does this MR do and why?
The strong_memoize
is one of the most frequently used methods.
Doing a few improvements we can significantly reduce GC pressure (4x)
and CPU pressure (1.66x).
Comments:
- As shown by the https://gitlab-org.gitlab.io/-/gitlab/-/jobs/2147998302/artifacts/coverage/index.html#1d4d1359e610fa76c91f0dd77b07994b9140eb42 the
strong_memoize
is called40_501_201
times for specs... - The main usage pattern of
strong_memoize
is to passstrong_memoize(:name)
, but thestrong_memoize("name")
is possible as well - The change retains and tests both (string and symbol)
- Previously, each call would use 4 heap slots, since the
ivar
was executed 2x - Previously, the
ivar
would doname.to_s
and create a new string with"@#{name}"
, this for symbol would result in two heap slots - Now, the implementation is optimised to have exactly single allocation
- Now, the implementation is optimised to call
ivar
exactly once
Benchmark
#strong_memoize
memory allocation
for Symbol
performant
user system total real
legacy non-nil 0.742533 0.013884 0.756417 ( 0.756915)
legacy nil 0.734473 0.000594 0.735067 ( 0.735587)
new non-nil 0.463679 0.003887 0.467566 ( 0.468097)
new nil 0.441397 0.000000 0.441397 ( 0.441593)
for String
performant
user system total real
legacy non-nil 0.567696 0.000000 0.567696 ( 0.568327)
legacy nil 0.564160 0.000000 0.564160 ( 0.564414)
new non-nil 0.447927 0.000000 0.447927 ( 0.448299)
new nil 0.439982 0.000000 0.439982 ( 0.440183)
benchmark.rb
[:method_name, "method_name"].each do |argument|
context "for #{argument.class}" do
it 'performant' do
n = 1_000_000
result = Benchmark.bm(14) do |x|
x.report("legacy non-nil") do
object.clear_memoization(argument)
n.times do
object.legacy_strong_memoize(argument) { 10 }
end
end
x.report("legacy nil") do
object.clear_memoization(argument)
n.times do
object.legacy_strong_memoize(argument) { nil }
end
end
x.report("new non-nil") do
object.clear_memoization(argument)
n.times do
object.strong_memoize(argument) { 10 }
end
end
x.report("new nil") do
object.clear_memoization(argument)
n.times do
object.strong_memoize(argument) { nil }
end
end
end
end
end
end
MR acceptance checklist
This checklist encourages us to confirm any changes have been analyzed to reduce risks in quality, performance, reliability, security, and maintainability.
-
I have evaluated the MR acceptance checklist for this MR.
Edited by Kamil Trzciński (Back 2025-01-01)