Store known span tags densely in TagMap by tag-id#11814
Conversation
🟢 Java Benchmark SLOs — All performance SLOs passed
PR vs. master results
Commit: Load and DaCapo benchmarks can be triggered manually in the GitLab pipeline. Results will appear in the Benchmarking Platform UI after completion. |
27d42d4 to
cd8b73a
Compare
Directional span-creation benchmark: dense off vs on (this branch)Toggle A/B on this branch ( Config:
Honest read: dense in isolation is alloc-negative here (~+0.5–1.5%)This is a local valley, not the destination — and the cause is instructive:
Reconciles with the macro From Claude: directional A/B run during the dense reconcile onto the level-split stack; posting the honest before/after and the coupled-gating that gets us out of the valley. 🤖 Generated with Claude Code |
cd8b73a to
a5e12e4
Compare
…scan) A per-map long bitmask (knownBloom) over the dense store: a set bit means a tagId MAY be present (scan to confirm), a clear bit means DEFINITELY absent — so the common per-build insert skips the linear knownIndexOf scan and appends in O(1). Crude position->bit map (fieldPos & 63); a collision-minimizing per-type coloring later only raises the hit rate — correctness never depends on it because the scan stays authoritative. Superset semantics: set on add, never cleared on remove (a stale bit costs a scan, never a wrong answer). Alloc-neutral (one long field, no extra allocation); the win is insertion CPU, moving the dense store toward HashMap insertion parity without the scan. Reconciled onto the folded-class dense store (#11814). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This comment has been minimized.
This comment has been minimized.
Known tags (keyOf resolves to a stored id) are held in insertion-ordered parallel arrays (knownIds/knownValues) with NO per-tag Entry object — the allocation lever. Lazily allocated on the first known-tag write; custom tags stay in the hash buckets. Disjoint by construction (known-ness is global), so read-through shadow checks stay within-region and the bucket path is unchanged. - KnownTagCodec (id encoding + resolver) + hand-written KnownTags (keyOf substrate over StringIndex). Off-by-default: dormant until a resolver registers, so production is byte-identical. - CoreTracer flips it live behind `-Ddd.trace.dense.tags.enabled` for A/B. - Sizing is a generous fixed stopgap (KNOWN_INIT_CAP=12, the per-type max); exact per-type sizing comes with the tag registry. Reconciled onto the level-split stack (fold + read-through + StringIndex); built on the folded final-class TagMap. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
a5e12e4 to
6c70848
Compare
…scan) A per-map long bitmask (knownBloom) over the dense store: a set bit means a tagId MAY be present (scan to confirm), a clear bit means DEFINITELY absent — so the common per-build insert skips the linear knownIndexOf scan and appends in O(1). Crude position->bit map (fieldPos & 63); a collision-minimizing per-type coloring later only raises the hit rate — correctness never depends on it because the scan stays authoritative. Superset semantics: set on add, never cleared on remove (a stale bit costs a scan, never a wrong answer). Alloc-neutral (one long field, no extra allocation); the win is insertion CPU, moving the dense store toward HashMap insertion parity without the scan. Reconciled onto the folded-class dense store (#11814). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Draft for preservation — not for merge yet. Per plan: lands after next week's release, with both the read-through and dense pieces behind experimental flags (off by default, flipped on internally to bake).
This branch is stacked: read-through (level-split) commits + the dense known-tag store on top.
Read-through (level-split phase 1) — stacked, tracked separately
The mechanism is draft #11789. This branch also carries the consumer wiring (
mergedTracerTagsas a read-through parent) + benchmark; those will be split into the read-through PR(s), which land first. Still TODO: add an experimental flag gating the read-through parent wiring (currently gated only on!needsIntercept).Dense known-tag store (this PR's focus)
Known tags (those
KnownTags.keyOfresolves to a stored id) store in dense parallel arrays (knownIds/knownValues, insertion-ordered, cap-8 ×2) with no per-tagEntry— the #1 tracer allocator. Coexists with the hash buckets (arbitrary tags); disjoint by global known-ness, so read-through shadow checks stay within-region.KnownTags(tagId encoding) +KnownTagIds(registry + open-addressedkeyOfviaStringIndex)OptimizedTagMap(set/get/remove/forEach/size/copy/putAll/clear/iterate)EMPTY_BUCKETSsentinel + copy-on-write; all-known maps allocate zero bucketsEntryReaderiterator — alloc-free dense reads on the serialize path;entrySet()keeps real retain-safeEntrydd.trace.dense.tags.enabled(experimental system property; → promote to a Config flag before ship)Validation
-prof gc(real resolver, realistic mixed span): per-build alloc −8% (7 tags) → −41% (12 tags); all-known / trace-tier shape −57%/−43%.OptimizedTagMapDenseForkedTest(real registry + read-through union),TagMapDenseFuzzForkedTest(3 key regimes, HashMap oracle +checkIntegrity),DenseStoreAllocBenchmark.The CPU upside (skip
keyOfby setting tags viasetTag(KnownTagIds.X)) arrives via incremental instrumentation migration over time — no big-bang.🤖 Generated with Claude Code