Repository navigation
Conversation
…sed invalidation Builds on the :if/:unless memoization fix. Field#compile resolves each field's per-field decisions (extractor, datetime formatting, defaults, :if/:unless) once into a closure, with a fast path for plain fields (public_send/[] read, no conditions/format/default). ViewCollection caches the compiled closures per view alongside :fields/:transformers, and rendering drives object_to_hash from them, hoisting the per-view lookups out of the per-object loop. To keep this safe, Blueprinter::Configuration gains a generation counter bumped by the compiled-attribute writers and reset_configuration!; ViewCollection keys its cache on it and Field keys its condition memoization on it, so global config (datetime_format, field_default, :if/:unless) changed after a render still takes effect on the next render. Attributes are split into COMPILED_ATTRIBUTES (cache invalidating) and LIVE_ATTRIBUTES (re-read per render). ~2.7x faster renders on top of the memoization fix (~4.5x vs main). Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: hamdy <hamdy.abdelbadeea@procore.com>
3 tasks done
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.
Checklist:
Summary
Builds on the memoization fix.
Field#compileresolves each field's per-field decisions — extractor, datetime formatting, defaults,:if/:unless— once into a closure, with a fast path for plain fields (public_send/[]read, no conditions/format/default).ViewCollectioncaches the compiled closures per view alongside:fields/:transformers, and rendering drivesobject_to_hashfrom them, hoisting the per-view lookups out of the per-object loop.To keep the cache honest,
Blueprinter::Configurationgains a generation counter bumped by the compiled-attribute writers andreset_configuration!.ViewCollectionkeys its cache on it andFieldkeys its condition memoization on it, so global config (datetime_format,field_default,:if/:unless) changed after a render still takes effect on the next render. Config attributes are split intoCOMPILED_ATTRIBUTES(cache-invalidating) andLIVE_ATTRIBUTES(re-read per render).Results
Measured with this repo's
spec/benchmarks/throughput_test.rb(Ruby 3.3.9), median of 3 runs, same harness across all three refs:main(85ce233),hamdy/fix-memoization(procore-oss#608,2a24d75), and this branch (d98e6b1).mainhas no benchmark harness, so it was run against a copy of this branch's harness.All three refs
mainhamdy/fix-memoization(procore-oss#608)This PR vs
hamdy/fix-memoization— what this PR addsThis PR vs
main— cumulative (memoization + compile pipeline) 🔥mainThe two optimizations are roughly multiplicative: the memoization fix removes per-object condition re-resolution (~1.67×), and the compile fast path removes per-object extractor branching on top of that (~2.7×), for a collective ~4.6× vs
main. These are single-machine wall-clock numbers with normal jitter — read them to ~2 significant figures.Correctness — the invalidation caveat
The compiled closures bake in global config resolved at first render, so a stale cache would be a real behaviour bug. The generation counter prevents it, verified end-to-end:
spec/integrations/base_spec.rb— render once, then set globalfield_default/datetime_format/:if; the next render reflects it.spec/units/view_collection_spec.rb— the compiled cache rebuilds when the generation changes.spec/units/field_spec.rb— condition memoization re-resolves on generation change, and does not re-resolve while it is unchanged (the hot path).spec/units/configuration_spec.rb— everyCOMPILED_ATTRIBUTESwriter bumps the generation; noLIVE_ATTRIBUTESwriter does.The fast path is behaviourally equivalent to the extractor path for plain fields: with no format/default configured,
AutoExtractor#extractreduces to the rawpublic_send/[]read.Verification
No public API change. No version bump.