@@ -1225,11 +1225,11 @@ static final class EmptyHolder {
12251225
12261226 /**
12271227 * Dense known-tag store (dense-tagmap-design §5). Values for KNOWN tags (those {@link
1228- * KnownTagCodec#keyOf} resolves to a stored id) live in these INSERTION-ORDERED parallel arrays with
1229- * NO per-tag {@link Entry} object — the allocation win. Lazily allocated on the first known-tag
1230- * write ({@code null} until then, so all-unknown maps pay nothing) and grown x2 from {@link
1231- * #KNOWN_INIT_CAP}. Matched by globalSerial via a linear scan ({@link #knownIndexOf}); reads
1232- * aren't hot, so O(knownCount) is fine and positional indexing is deferred. Dormant until a
1228+ * KnownTagCodec#keyOf} resolves to a stored id) live in these INSERTION-ORDERED parallel arrays
1229+ * with NO per-tag {@link Entry} object — the allocation win. Lazily allocated on the first
1230+ * known-tag write ({@code null} until then, so all-unknown maps pay nothing) and grown x2 from
1231+ * {@link #KNOWN_INIT_CAP}. Matched by globalSerial via a linear scan ({@link #knownIndexOf});
1232+ * reads aren't hot, so O(knownCount) is fine and positional indexing is deferred. Dormant until a
12331233 * resolver is registered: {@code keyOf} returns 0, so nothing routes here and production is
12341234 * byte-identical.
12351235 *
@@ -1247,6 +1247,15 @@ static final class EmptyHolder {
12471247 private Object [] knownValues ;
12481248 private int knownCount ;
12491249
1250+ /**
1251+ * Bloom-style presence filter over the dense store: a set bit means {@code tagId} MAY be present
1252+ * (scan to confirm), a clear bit means it is DEFINITELY absent (skip the scan, append in O(1) —
1253+ * the common per-build case). A superset of the present ids' bits: set on every add, never
1254+ * cleared on remove (a stale bit only costs a scan, never a wrong answer). So correctness never
1255+ * depends on the position→bit collision rate; only the fast-path hit rate does.
1256+ */
1257+ private long knownBloom ;
1258+
12501259 private static final int KNOWN_INIT_CAP = 8 ;
12511260
12521261 /**
@@ -1552,27 +1561,45 @@ private void ensureKnownCapacity() {
15521561 }
15531562 }
15541563
1564+ /**
1565+ * Presence-filter bit for {@code tagId}. Crude position→bit map ({@code fieldPos} mod 64) to
1566+ * start; a collision-minimizing per-type coloring later only raises the hit rate — correctness
1567+ * never depends on it, because the scan is authoritative.
1568+ */
1569+ private static long knownBloomBit (long tagId ) {
1570+ return 1L << (KnownTagCodec .fieldPos (tagId ) & 63 );
1571+ }
1572+
15551573 /**
15561574 * Stores a known tag's value densely (no {@link Entry} alloc). Overwrites in place when present
15571575 * (returning the prior value materialized as an Entry, per the {@code Map} contract — usually
1558- * discarded by {@code set}); otherwise appends, growing x2 as needed.
1576+ * discarded by {@code set}); otherwise appends, growing x2 as needed. The bloom filter skips the
1577+ * {@link #knownIndexOf} scan when the tag is definitely absent (the common per-build case), so an
1578+ * append is O(1) instead of O(n).
15591579 */
15601580 private Entry putKnownValue (long tagId , Object value ) {
1561- int i = this .knownIndexOf (tagId );
1562- if (i >= 0 ) {
1563- Object prior = this .knownValues [i ];
1564- this .knownValues [i ] = value ;
1565- return materializeKnown (tagId , prior );
1581+ long bit = knownBloomBit (tagId );
1582+ if ((this .knownBloom & bit ) != 0 ) {
1583+ // maybe present -> scan to overwrite in place
1584+ int i = this .knownIndexOf (tagId );
1585+ if (i >= 0 ) {
1586+ Object prior = this .knownValues [i ];
1587+ this .knownValues [i ] = value ;
1588+ return materializeKnown (tagId , prior );
1589+ }
1590+ // bloom false positive (collision) -> fall through to append
15661591 }
15671592 this .ensureKnownCapacity ();
15681593 int slot = this .knownCount ++;
15691594 this .knownIds [slot ] = tagId ;
15701595 this .knownValues [slot ] = value ;
1596+ this .knownBloom |= bit ;
15711597 return null ;
15721598 }
15731599
15741600 /** Raw dense value for {@code tagId}, or {@code null} when absent (no Entry, no boxing). */
15751601 private Object knownRawValue (long tagId ) {
1602+ if ((this .knownBloom & knownBloomBit (tagId )) == 0 ) return null ; // definitely absent, no scan
15761603 int i = this .knownIndexOf (tagId );
15771604 return i < 0 ? null : this .knownValues [i ];
15781605 }
@@ -1581,6 +1608,7 @@ private Object knownRawValue(long tagId) {
15811608 * Removes a known tag from the dense store (swap-with-last), returning the prior Entry or null.
15821609 */
15831610 private Entry removeKnown (long tagId ) {
1611+ if ((this .knownBloom & knownBloomBit (tagId )) == 0 ) return null ; // definitely absent
15841612 int i = this .knownIndexOf (tagId );
15851613 if (i < 0 ) return null ;
15861614 Object prior = this .knownValues [i ];
@@ -1589,6 +1617,8 @@ private Entry removeKnown(long tagId) {
15891617 this .knownValues [i ] = this .knownValues [last ];
15901618 this .knownIds [last ] = 0L ;
15911619 this .knownValues [last ] = null ;
1620+ // knownBloom intentionally NOT cleared: a stale-set bit only costs a scan; clearing could drop
1621+ // a bit still shared (via collision) by a present id -> false negative.
15921622 return materializeKnown (tagId , prior );
15931623 }
15941624
@@ -1603,7 +1633,8 @@ private static Entry materializeKnown(long tagId, Object value) {
16031633 * local bucket entry — known tags never bucket — so no bucket check is needed here.)
16041634 */
16051635 private boolean parentDenseHidden (long tagId ) {
1606- if (this .knownIndexOf (tagId ) >= 0 ) return true ; // shadowed by a local dense entry
1636+ // shadowed by a local dense entry (bloom prunes the scan when definitely absent)
1637+ if ((this .knownBloom & knownBloomBit (tagId )) != 0 && this .knownIndexOf (tagId ) >= 0 ) return true ;
16071638 return this .removedFromParent != null
16081639 && this .removedFromParent .contains (KnownTagCodec .nameOf (tagId )); // tombstoned
16091640 }
@@ -2006,6 +2037,7 @@ private void putAllIntoEmptyMap(OptimizedTagMap that) {
20062037 this .knownIds = Arrays .copyOf (that .knownIds , that .knownIds .length );
20072038 this .knownValues = Arrays .copyOf (that .knownValues , that .knownValues .length );
20082039 this .knownCount = that .knownCount ;
2040+ this .knownBloom = that .knownBloom ;
20092041 }
20102042 }
20112043
@@ -2048,7 +2080,8 @@ public void fillStringMap(Map<? super String, ? super String> stringMap) {
20482080 }
20492081 for (int i = 0 ; i < this .knownCount ; ++i ) {
20502082 stringMap .put (
2051- KnownTagCodec .nameOf (this .knownIds [i ]), TagValueConversions .toString (this .knownValues [i ]));
2083+ KnownTagCodec .nameOf (this .knownIds [i ]),
2084+ TagValueConversions .toString (this .knownValues [i ]));
20522085 }
20532086 }
20542087
@@ -2393,6 +2426,7 @@ public void clear() {
23932426 this .knownIds = null ;
23942427 this .knownValues = null ;
23952428 this .knownCount = 0 ;
2429+ this .knownBloom = 0L ;
23962430 }
23972431
23982432 public OptimizedTagMap freeze () {
0 commit comments