Skip to content

Commit 75950be

Browse files
dougqhclaude
andcommitted
Add bloom-filter fast-path to the dense tag store (skip knownIndexOf 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>
1 parent 0c0d740 commit 75950be

1 file changed

Lines changed: 40 additions & 7 deletions

File tree

  • internal-api/src/main/java/datadog/trace/api

internal-api/src/main/java/datadog/trace/api/TagMap.java

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,6 +1038,15 @@ public EntryChange next() {
10381038
private Object[] knownValues;
10391039
private int knownCount;
10401040

1041+
/**
1042+
* Bloom-style presence filter over the dense store: a set bit means {@code tagId} MAY be present
1043+
* (scan to confirm), a clear bit means it is DEFINITELY absent (skip the scan, append in O(1) —
1044+
* the common per-build case). A superset of the present ids' bits: set on every add, never
1045+
* cleared on remove (a stale bit only costs a scan, never a wrong answer). So correctness never
1046+
* depends on the position→bit collision rate; only the fast-path hit rate does.
1047+
*/
1048+
private long knownBloom;
1049+
10411050
private static final int KNOWN_INIT_CAP =
10421051
12; // generous per-type max stopgap; exact per-type sizing comes with the tag registry
10431052

@@ -1354,27 +1363,45 @@ private void ensureKnownCapacity() {
13541363
}
13551364
}
13561365

1366+
/**
1367+
* Presence-filter bit for {@code tagId}. Crude position→bit map ({@code fieldPos} mod 64) to
1368+
* start; a collision-minimizing per-type coloring later only raises the hit rate — correctness
1369+
* never depends on it, because the scan is authoritative.
1370+
*/
1371+
private static long knownBloomBit(long tagId) {
1372+
return 1L << (KnownTagCodec.fieldPos(tagId) & 63);
1373+
}
1374+
13571375
/**
13581376
* Stores a known tag's value densely (no {@link Entry} alloc). Overwrites in place when present
13591377
* (returning the prior value materialized as an Entry, per the {@code Map} contract — usually
1360-
* discarded by {@code set}); otherwise appends, growing x2 as needed.
1378+
* discarded by {@code set}); otherwise appends, growing x2 as needed. The bloom filter skips the
1379+
* {@link #knownIndexOf} scan when the tag is definitely absent (the common per-build case), so an
1380+
* append is O(1) instead of O(n).
13611381
*/
13621382
private Entry putKnownValue(long tagId, Object value) {
1363-
int i = this.knownIndexOf(tagId);
1364-
if (i >= 0) {
1365-
Object prior = this.knownValues[i];
1366-
this.knownValues[i] = value;
1367-
return materializeKnown(tagId, prior);
1383+
long bit = knownBloomBit(tagId);
1384+
if ((this.knownBloom & bit) != 0) {
1385+
// maybe present -> scan to overwrite in place
1386+
int i = this.knownIndexOf(tagId);
1387+
if (i >= 0) {
1388+
Object prior = this.knownValues[i];
1389+
this.knownValues[i] = value;
1390+
return materializeKnown(tagId, prior);
1391+
}
1392+
// bloom false positive (collision) -> fall through to append
13681393
}
13691394
this.ensureKnownCapacity();
13701395
int slot = this.knownCount++;
13711396
this.knownIds[slot] = tagId;
13721397
this.knownValues[slot] = value;
1398+
this.knownBloom |= bit;
13731399
return null;
13741400
}
13751401

13761402
/** Raw dense value for {@code tagId}, or {@code null} when absent (no Entry, no boxing). */
13771403
private Object knownRawValue(long tagId) {
1404+
if ((this.knownBloom & knownBloomBit(tagId)) == 0) return null; // definitely absent, no scan
13781405
int i = this.knownIndexOf(tagId);
13791406
return i < 0 ? null : this.knownValues[i];
13801407
}
@@ -1383,6 +1410,7 @@ private Object knownRawValue(long tagId) {
13831410
* Removes a known tag from the dense store (swap-with-last), returning the prior Entry or null.
13841411
*/
13851412
private Entry removeKnown(long tagId) {
1413+
if ((this.knownBloom & knownBloomBit(tagId)) == 0) return null; // definitely absent
13861414
int i = this.knownIndexOf(tagId);
13871415
if (i < 0) return null;
13881416
Object prior = this.knownValues[i];
@@ -1391,6 +1419,8 @@ private Entry removeKnown(long tagId) {
13911419
this.knownValues[i] = this.knownValues[last];
13921420
this.knownIds[last] = 0L;
13931421
this.knownValues[last] = null;
1422+
// knownBloom intentionally NOT cleared: a stale-set bit only costs a scan; clearing could drop
1423+
// a bit still shared (via collision) by a present id -> false negative.
13941424
return materializeKnown(tagId, prior);
13951425
}
13961426

@@ -1405,7 +1435,8 @@ private static Entry materializeKnown(long tagId, Object value) {
14051435
* local bucket entry — known tags never bucket — so no bucket check is needed here.)
14061436
*/
14071437
private boolean parentDenseHidden(long tagId) {
1408-
if (this.knownIndexOf(tagId) >= 0) return true; // shadowed by a local dense entry
1438+
// shadowed by a local dense entry (bloom prunes the scan when definitely absent)
1439+
if ((this.knownBloom & knownBloomBit(tagId)) != 0 && this.knownIndexOf(tagId) >= 0) return true;
14091440
return this.removedFromParent != null
14101441
&& this.removedFromParent.contains(KnownTagCodec.nameOf(tagId)); // tombstoned
14111442
}
@@ -1801,6 +1832,7 @@ private void putAllIntoEmptyMap(TagMap that) {
18011832
this.knownIds = Arrays.copyOf(that.knownIds, that.knownIds.length);
18021833
this.knownValues = Arrays.copyOf(that.knownValues, that.knownValues.length);
18031834
this.knownCount = that.knownCount;
1835+
this.knownBloom = that.knownBloom;
18041836
}
18051837
}
18061838

@@ -2182,6 +2214,7 @@ public void clear() {
21822214
this.knownIds = null;
21832215
this.knownValues = null;
21842216
this.knownCount = 0;
2217+
this.knownBloom = 0L;
21852218
}
21862219

21872220
public TagMap freeze() {

0 commit comments

Comments
 (0)