-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
(Further) Improve Bundler::Settings#[] performance and memory usage #6923
(Further) Improve Bundler::Settings#[] performance and memory usage #6923
Conversation
I previously identified and improved this method over in rubygems#6884 but while reviewing another memory_profiler profile, I realized another gain we can eek out. This method keeps comes up in part because `configs` is allocating a new Hash every time. My last change took advantage of that by using `map!` on it. `configs` is called quite often, including in this `[]` method, so there's a benefit to memoizing it. Back in `[]`, logically we are trying to find the first Hash in `configs` that has a value for the given key. Currently, we end up `map` and `compact` to just get that value. Instead, we can use a loop over `configs`, and break when we find the value for the key.
I'm also including the results of memory_profiler on our app boot, focused on memory_profiler. The main data point to note is the difference in memory allocated in Before:
After:
That is a 54.3% reduction in allocated memory 😍 |
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.
Nice. Btw why isn't there a "return result of first truthy block" on Enumerable. I often need it.
Based on the variety of test failures, the tests must need that memoized state cleared between runs. |
Yep, it is
Hmm, I will check it out. I think it's still an improvement w/o the memoization if it comes to it though. |
I tried reverting the memoization, and these are still failing, so it's something else. For reference, these are the failing ones:
I personally find some of the failures tough to parse in general especially when they are spawning new processes to do the work. If there are any protips, would love to hear them 😁 I think |
I'm wishing for Anyway, I tried the code and I get the same errors. Then I tried putting both old and new in the same file. The keys it fails on are set to |
Does |
Co-authored-by: Martin Emde <martinemde@users.noreply.github.com>
@@ -316,7 +318,7 @@ def key_for(key) | |||
private | |||
|
|||
def configs | |||
{ | |||
@configs ||= { |
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.
is there ever a time when we would want to recompute this ivar?
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.
alternatively, what if this became an each_config
method, yielding the key, value pairs? then we dont need the hash at all, if all we ever do is iterate over it
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.
is there ever a time when we would want to recompute this ivar?
I gave this some thought initally, but forgot to note it. I don't think so though.
My initial thought that was that it wouldn't need to reset if the contents of the ivars in it change (ie @temporary
and all). On closer review, the contents of the ivars could change (ie @temporary = {}
), but I don't see any writer methods or any code that assigns directly to them.
alternatively, what if this became an each_config method, yielding the key, value pairs? then we dont need the hash at all, if all we ever do is iterate over it
Oh, that is an interesting thought! I tested it, and yeah, it saves allocating a hash and is faster 😮 Updated my benchmark with it https://gist.github.com/technicalpickles/91933d34dcd47bfc99d6ac0e53fcdb1e (as correct manual_loop w/ each_config
)
@indirect oh interesting, I hadn't considered using a I added this implementation to the benchmark: key = key_for(name)
value = configs.flat_map {|_, config| break config[key] unless config[key].nil? }
converted_value(value, name) Unfortunately, it uses more memory and is slow though 😞 See 'with_flat_map' on https://gist.github.com/technicalpickles/91933d34dcd47bfc99d6ac0e53fcdb1e |
…et-operator-performance (Further) Improve Bundler::Settings#[] performance and memory usage (cherry picked from commit 54032f3)
…et-operator-performance (Further) Improve Bundler::Settings#[] performance and memory usage (cherry picked from commit 54032f3)
What was the end-user or developer problem that led to this PR?
It's ya boi technicalpickles running memory_profiler on a large monolith booting Rails again
What is your fix for the problem, implemented in this PR?
I previously identified and improved this method over in #6884 but while reviewing another memory_profiler profile, I realized another gain we can eek out.
This method keeps comes up in part because
configs
is allocating a new Hash every time. My last change took advantage of that by usingmap!
on it.configs
is called quite often, including in this[]
method, so there's a benefit to memoizing it.Back in
[]
, logically we are trying to find the first Hash inconfigs
that has a value for the given key. Currently, we end upmap
andcompact
to just get that value.Instead, we can use a loop over
configs
, and break when we find the value for the key.Benchmarks
Microbenchmarks, comparing implementations (this PR implements manual_loop w/ memoized configs):
Hyperfine running against bundler 2.4.19 vs this branch
Colorful version:
Make sure the following tasks are checked