Skip to content

Prototype #602: replace Vector[Any] with cons-cell RuntimeDataStore - #949

Merged
MateuszKubuszok merged 6 commits into
masterfrom
prototype-602-dsl-allocation-optimization
Sep 2, 2026
Merged

MateuszKubuszok merged 6 commits into
masterfrom
prototype-602-dsl-allocation-optimization

Conversation

@MateuszKubuszok

@MateuszKubuszok MateuszKubuszok commented Sep 2, 2026

Copy link
Copy Markdown
Member

Summary

  • Replaces Vector[Any] runtime data store with a custom cons-cell linked list (RuntimeDataStore) that materializes into a flat Array[Any] on first indexed access
  • Prepend is O(1) — a single object allocation per DSL step (vs. Vector's copy-on-prepend)
  • Branching is safeval-captured definitions share the immutable cons chain; each branch gets its own materialized array
  • Thread-safe via benign race on materialization
  • Updates docs (DESIGN.md, index.md, under-the-hood.md) to reflect the new backing store
  • Adds toString for debuggability

Compile-time DSL chain flattening

The terminal macros (.transform, .buildTransformer, .patch) now analyze the prefix tree at compile time and emit optimized RuntimeDataStore construction:

Pattern Optimization Allocations
Full linear chain (define.withFieldConst(...).buildTransformer) RuntimeDataStore.wrap(Array(...)) 1 array
Partial chain (val b = define.withFieldConst(...); b.withFieldConst(...).buildTransformer) b.runtimeData.prependedAll(Array(...)) 1 array + 1 flat store
Flag wrappers (.enableDefaultValues, .withTargetFlag, etc.) Transparent — walk recurses through wrappers Same as underlying chain
No optimization (opaque base, no collected data) Fallback to prefix.runtimeData Cons-cell chain (unchanged)

The walk uses hasEmptyOverrides (checks for TransformerOverrides.Empty / PatcherOverrides.Empty type args) to distinguish extension methods (valid chain bases) from flag wrappers (must recurse through). Works on both Scala 2.13 and Scala 3.

Benchmark results (JMH, Scala 2.13, 22-field case class)

Benchmark                              BEFORE (ops/ms)   AFTER (ops/ms)   Change
────────────────────────────────────   ───────────────   ──────────────   ──────
largeConstChimneyIntoSplit                  16,444           25,275       +54%
largeConstChimneyDefinedSplit              15,650           26,014       +66%
largeConstChimneyInto                      16,582           16,635        ~0%
largeConstChimneyDefined                   83,904           91,586        ~0%
largeComputeChimneyInto                    17,124           17,163        ~0%
largeRenameChimneyInto                    180,523          178,944        ~0%
largeConstByHand (baseline)               183,157          188,010        ~0%

Addresses #602.

Test plan

  • 1236 tests pass on Scala 3.9.0 and Scala 2.13.18 (core + allocation specs)
  • 312 cats integration tests pass
  • 39 protobufs integration tests pass
  • 70 java-collections integration tests pass
  • 19 RuntimeDataStoreSpec tests (including 5 prependedAll tests)
  • 66 allocation-correctness tests across 6 spec files
  • JMH benchmarks show 54-66% improvement on partial chains, no regression on linear chains
  • Scala.js / Scala Native build verification (not yet run)

Replace the Vector[Any]-backed RuntimeDataStore with a custom cons-cell
linked list that materializes to an Array on first indexed access.
This eliminates the O(n) array copy on every .withFieldX DSL call,
reducing chain construction from O(n^2) to O(n) total allocations.

- New RuntimeDataStore class: O(1) prepend via cons cells, O(1) random
  access after lazy materialization to Array on first .apply() call
- Updated type aliases in TransformerDefinitionCommons and
  PatcherDefinitionCommons (both Scala 2 and 3) to use the new class
- Changed all addOverride implementations from `overrideData +: runtimeData`
  to `runtimeData.prepended(overrideData)`
- Fixed DslMacros cross-quote splices for the same pattern
- Added RuntimeDataStoreSpec with unit tests for core operations
Cover TransformerDefinition, TransformerInto, PartialTransformerDefinition,
PartialTransformerInto, PatcherDefinition, and PatcherUsing with tests for:
- 0, 1, and multiple data-carrying modifiers
- data-carrying interleaved with type-only modifiers (withFieldRenamed,
  enableMethodAccessors, etc.)
- val-reference branching (shared base, two independent continuations)
- deep chains after branching
- Fix broken [[prepend]] scaladoc link → [[prepended]]
- Fix @SInCE 1.7.0 → @SInCE 2.0.0
- Tone down overstated perf justification in scaladoc
- Update DESIGN.md, index.md, under-the-hood.md: Vector[Any] → RuntimeDataStore
- Add RuntimeDataStore.toString for debuggability
- Add tests: repeated apply (materialized fast path), materialize-then-branch,
  toString, built transformer used multiple times
- Fix scalafmt formatting in test scaladocs
@MateuszKubuszok MateuszKubuszok linked an issue Sep 2, 2026 that may be closed by this pull request
@codecov

codecov Bot commented Sep 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 73.08970% with 81 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.74%. Comparing base (b128e63) to head (32762bc).
⚠️ Report is 3 commits behind head on master.

Files with missing lines Patch % Lines
...letime/derivation/RuntimeDataStoreFlattening.scala 43.10% 33 Missing ⚠️
...letime/derivation/RuntimeDataStoreFlattening.scala 63.51% 27 Missing ⚠️
...ime/derivation/transformer/TransformerMacros.scala 78.75% 17 Missing ⚠️
...imney/dsl/PartialTransformerDefinitionForAll.scala 0.00% 1 Missing ⚠️
...land/chimney/dsl/TransformerDefinitionForAll.scala 0.00% 1 Missing ⚠️
...compiletime/derivation/patcher/PatcherMacros.scala 94.73% 1 Missing ⚠️
...imney/dsl/PartialTransformerDefinitionForAll.scala 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #949      +/-   ##
==========================================
- Coverage   83.46%   82.74%   -0.73%     
==========================================
  Files         176      183       +7     
  Lines        7011     7331     +320     
  Branches      509      529      +20     
==========================================
+ Hits         5852     6066     +214     
- Misses       1159     1265     +106     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Replace @volatile with benign-race pattern (removes 25% regression on
pre-built transformer hot path). Add RuntimeDataStore.wrap(Array) factory
for pre-materialized stores with System.arraycopy support for branching.

Introduce RuntimeDataStoreFlattening on both Scala 2 and Scala 3: the
terminal macro walks the prefix tree to detect linear WithRuntimeDataStore
.update chains, and when found emits RuntimeDataStore.wrap(Array(...))
instead of N cons-cell allocations. Falls back to the standard
prefix.runtimeData access for unrecognized tree shapes (val captures,
forAll-produced constructors with embedded data, etc.).

Guard the New-constructor base case with hasEmptyRDS to avoid incorrectly
flattening forAll chains where runtime data is embedded in constructor
args rather than delivered via WithRuntimeDataStore.update.
…ppers

The walk function now returns a tri-state (EmptyBase/OpaqueBase) instead
of Boolean, enabling three optimization tiers:

- Full linear chain → RuntimeDataStore.wrap(Array(...))
- Partial chain (val capture) → base.runtimeData.prependedAll(Array(...))
- Flag wrappers → transparent recursion via hasEmptyOverrides check

JMH shows 54-66% throughput improvement on partial chain benchmarks
(largeConstChimneyIntoSplit, largeConstChimneyDefinedSplit) with no
regression on existing linear chain benchmarks.
@MateuszKubuszok
MateuszKubuszok merged commit cbd7321 into master Sep 2, 2026
27 of 31 checks passed
@MateuszKubuszok
MateuszKubuszok deleted the prototype-602-dsl-allocation-optimization branch September 2, 2026 10:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Possible performance improvements

1 participant