Build and memoize i18n keys lazily to reduce Value memory#478
Open
connorshea wants to merge 2 commits into
Open
Build and memoize i18n keys lazily to reduce Value memory#478connorshea wants to merge 2 commits into
connorshea wants to merge 2 commits into
Conversation
Each Enumerize::Value eagerly built and retained an array of i18n lookup keys plus a humanized fallback string in its constructor, regardless of whether #text was ever rendered. Build them lazily instead, and memoize the result on the (non-frozen) Attribute keyed by value name, so a value's keys are composed at most once and values whose #text is never displayed retain nothing. The cache is updated copy-on-write, so concurrent #text calls stay safe without locking, matching the thread-safety the frozen-at-boot version had. A race between two builds is last-writer-wins — always correct, at worst a redundant rebuild. When #text is never rendered, retained memory drops ~38% (~80 B/value) and class-definition-time allocations drop ~85%; rendered values match the old eager throughput. See benchmark/lazy_keys_benchmark.rb. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
This changeset was generated using Claude Code w/ Opus 4.8. This change is informed by usage in a production Rails app, and tested and reviewed by me manually.
Each
Enumerize::Valueeagerly built and retained an array of i18n lookup keys and a humanized fallback string in its constructor, regardless of whether#textwas ever actually rendered. For the case where the class is used as an intermediate value as part of a calculation, for example, and then only the final calculated result is rendered to the user, that is all wasted memory usage.This PR refactors the code to build them lazily instead, and memoizes the result on the (non-frozen)
Attributekeyed by value name. So a value's keys are composed at most once and values where#textis never displayed have no stored allocation.When
#textis never rendered, as in the benchmarks below, retained memory drops ~38% (~80 B/value) and allocations drop ~85% for class instantiation. Rendered values match the previous rate of throughput so there should be minimal performance penalty here.Benchmarking
benchmark script
Lazy + memoized i18n keys vs. the old eager-at-construction behavior. Memory is measured over 30,000
Valueobjects with#textnever called (the idle case the change targets); throughput is measured warm (keys memoized), the realistic render path.#textthroughput (i/s, 6 values/iter, warm)