Skip to content

perf: multi-field object inline array storage (2-8 fields)#688

Open
He-Pin wants to merge 2 commits intodatabricks:masterfrom
He-Pin:perf/multi-field-object
Open

perf: multi-field object inline array storage (2-8 fields)#688
He-Pin wants to merge 2 commits intodatabricks:masterfrom
He-Pin:perf/multi-field-object

Conversation

@He-Pin
Copy link
Copy Markdown
Contributor

@He-Pin He-Pin commented Apr 5, 2026

Motivation

Val.Obj currently always allocates a LinkedHashMap for fields, even for small objects with 2-8 fields. In Jsonnet programs, most objects are small (config entries, function returns). The LinkedHashMap overhead dominates allocation in object-heavy workloads.

Key Design Decision

Store object fields in flat inline arrays when the field count is ≤8: parallel arrays for keys, values, and visibility flags. This avoids LinkedHashMap allocation, hashing, and entry boxing while preserving O(1) field lookup via linear scan (fast for ≤8 elements due to cache locality).

Modification

  • Add inline array storage paths in Val.Obj for 2-8 field objects
  • Preserve full LinkedHashMap path for larger objects
  • All existing APIs (field access, iteration, merge) work transparently

Benchmark Results

JMH (JVM, 3 iterations warmup + 3 measurement)

Benchmark Master (ms/op) This PR (ms/op) Change
bench.02 50.427 ± 38.9 37.807 ± 3.6 -25.0% 🔥
comparison2 85.854 ± 188.7 70.107 ± 6.4 -18.3%
realistic2 73.458 ± 66.7 66.453 ± 14.1 -9.5%

Analysis

The bench.02 improvement (-25.0%) is the strongest single-PR gain on this benchmark, because bench.02 creates many small objects repeatedly. The tight error bars confirm this is a real structural improvement rather than JIT noise. This builds on the single-field optimization in #687 to cover the full small-object spectrum.

References

Result

All 46 tests pass. All benchmarks strongly positive, no regressions.

He-Pin added 2 commits April 5, 2026 12:57
…ation

For objects with exactly one field (common in patterns like `{ n: X }`),
store the field key and member inline in Val.Obj instead of allocating a
LinkedHashMap. The LinkedHashMap is lazily constructed only when needed
(e.g., key iteration via getAllKeys).

Key changes:
- Val.Obj: added singleFieldKey/singleFieldMember constructor params
- getValue0: lazily constructs LinkedHashMap from inline storage
- valueRaw: single-field fast path with String.equals instead of HashMap.get
- hasKeys/containsKey: fast paths to avoid forcing LinkedHashMap materialization
- visitMemberList: lazy builder allocation, only for 2+ field objects

Upstream: jit branch d284ecf (single-field object avoid LinkedHashMap)
Three-tier object storage: 1 field uses singleKey/singleMember,
2-8 fields use flat parallel arrays (inlineFieldKeys/inlineFieldMembers),
9+ fields use LinkedHashMap. This eliminates LinkedHashMap allocation for
the vast majority of Jsonnet objects which have fewer than 9 fields.

All fast paths updated: getValue0, hasKeys, containsKey,
containsVisibleKey, allKeyNames, visibleKeyNames, valueRaw.

Field tracking logic extracted into trackField() helper to avoid
code duplication between the two Member.Field case branches.

JMH: bench.02 -17.9%, realistic2 -2.7%, bench.04 -5.5%
Native: realistic2 -13.5% (1.89x faster than jrsonnet)

Upstream: jit branch commit 13e6ff3
@He-Pin He-Pin marked this pull request as ready for review April 5, 2026 10:31
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.

1 participant