Conversation
- Pass the args array to internal_get_or_set instead of re-splatting it, which allocated a second array on every call to a defined getter or setter - Detect setter names in method_missing with Symbol#name and end_with? instead of a regex, avoiding a String, MatchData, and capture per call - Look up each key once in apply_nested_hash, so a block default is no longer evaluated twice when a hash value is loaded over it With chef-config as the workload, method-style reads are ~15% faster and unknown keys read or set through method_missing are 16-21% faster. Signed-off-by: Tim Smith <tsmith84@proton.me>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Profiling mixlib-config with chef-config as the workload showed a few places where the library allocates more than it needs to on hot paths. This removes that overhead without changing behavior.
define_attr_accessor_methodscollected*argsand then re-splatted them intointernal_get_or_set(symbol, *args), allocating a second array on every call.internal_get_or_set(private) now takes the array directly.method_missing: setter names were detected withmethod_symbol.to_s =~ /(.+)=$/, which allocates a String, a MatchData, and a capture on every call. It now usesSymbol#name(a cached frozen string) withend_with?/chomp, with the same result for every name, including:==.apply_nested_hash: calledinternal_get(k.to_sym)up to twice per Hash-valued key, which evaluated block defaults twice when loading a hash (from YAML, JSON, TOML, orfrom_hash) over them. Each key is now looked up once. For example, chef-config'scache_optionsdefault block previously ran twice on the first load and now runs once.Benchmarks
Ruby 4.0.7, chef-config 19.3.15,
benchmark-ips:Config.chef_server_url(getter)Config.daemonize false(setter)method_missingmethod_missingConfig.knife[:editor](nested context)Config[:chef_server_url](control, unchanged)One allocation remains for getters because a
*argsparameter always allocates, even when nothing is passed. Removing it would mean dropping support for multi-argument setters likeConfig.foo 1, 2.Related findings (not changed here)
save(true)on chef-config runs at about 577 calls/s. 97% of that isFile.readable?/exist?/writable?calls inside chef-config's block defaults (for exampleuser_homeis evaluated 44 times per call). Block defaults are recomputed on every read by design, so any caching belongs in chef-config, not here.Testing
bundle exec rspec: 160 examples, 0 failuresTypes of changes