-
Notifications
You must be signed in to change notification settings - Fork 441
Optimize memory allocation when rendering partials #591
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Optimize memory allocation when rendering partials #591
Conversation
end | ||
|
||
set! name, value | ||
_set_value name, value |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can set the value directly here instead of going back in through set!
with different options. A call to set!
with these parameters will just end up calling _set_value
anyways.
This saves a bit in processing and also avoids an extra memory allocation for *args
.
options[:locals].merge! json: self | ||
@context.render options | ||
options[:locals][:json] = self | ||
@context.render options, nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The render
helper in rails will default the second parameter to {}
. By providing nil
here we save on that extra memory allocation.
That second parameter is intended to be the options you provide to the partial if the first param is the partial name (ex: render 'foo', options
). Since the partial name is included in the options
, that second parameter isn't actually used.
options
merging
We're seeing calls to
reverse_merge!
,merge!
, andmerge
fromJbuilderTemplate
come up as CPU and memory hot spots in our profiles.The changes proposed in this PR are inspired by https://github.com/fastruby/fast-ruby#hashmerge-vs-hash-code, and favours mutating the
options
hash via element assignment over merge methods. This saves on both CPU and memory allocation.Comparing
options[:locals].merge!(json: self)
tooptions[:locals][:json] = self
for example produced:This PR replaces all instances of
reverse_merge!
with[] ||=
, and all instances ofmerge!
with[]=
. Theoptions
were already being mutated so this introduces no change in behaviour.There are a handful of non-mutating calls to
merge
as well that I was hesitant to change, but upon further analysis theoptions
hash ends up being mutated further down the call chain anyways; any instance of theoptions
hash being merged are on code paths that render to partials which already mutate the options.I've run some benchmarks against something simple yet representative of a template structure that would exercise some of the changes being proposed.
The measurements below are for 100 posts, each with a single author.
CPU
Memory
I was surprised to see no difference in IPS given the earlier benchmarks, but that can be explained by
actionview
diluting it; this benchmark includes the entirerender
lifecycle which means options are being merged several millions fewer times per second.The impactful improvements it the ~20% in memory. Note that the memory allocation savings would depend entirely on your template - templates rendering to fewer or no partials would see less of an improvement, templates rendering to more partials could see a much larger improvement. As your API serves requests over time, this improvement would go a long way towards saving on garbage collection cycles.