Skip to content

Commit 66ee8ed

Browse files
dougqhclaude
andcommitted
Add TagSet + parallel int[] fixed-map option to UnsynchronizedMapBenchmark
Adds get_tagSetMap / get_tagSetMap_sameKey as a build-once, read-only map option in the map menu: keys in a TagSet, values in a parallel int[] (no boxing), get is Support.indexOf plus one array load. Fastest get in the benchmark and the tightest error bars; results captured in the javadoc. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 39c91c0 commit 66ee8ed

1 file changed

Lines changed: 56 additions & 0 deletions

File tree

internal-api/src/jmh/java/datadog/trace/util/UnsynchronizedMapBenchmark.java

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@
2424
* <li>TreeMap - better for custom Comparators - case-insensitive Maps (see
2525
* CaseInsensitiveMapBenchmark)
2626
* <li>LinkedHashMap - only when insertion order is needed
27+
* <li>TagSet + parallel value array - for a FIXED (build-once, read-only) map whose key set is
28+
* known up front: the keys go in a {@link TagSet} and the values in a parallel array indexed
29+
* by the slot {@code indexOf} returns. Fastest get, no per-lookup allocation, no node chasing
30+
* - but it can't change after construction (see get_tagSetMap).
2731
* </ul>
2832
*
2933
* <p>TagMap is the preferred way to store tags.
@@ -91,6 +95,23 @@
9195
* UnsynchronizedMapBenchmark.iterate_tagMap_forEach thrpt 6 422407681.630 ± 19493455.109 ops/s
9296
* UnsynchronizedMapBenchmark.iterate_treeMap thrpt 6 392884747.896 ± 80190674.417 ops/s
9397
* </code>
98+
*
99+
* <p>A TagSet + parallel int[] used as a fixed (build-once) map is the fastest get here -- ahead of
100+
* HashMap in both the rotating-key and same-key cases -- and the most predictable (±1-2% vs
101+
* HashMap's ~5% and TagMap's ~12%). It pays no boxing (int[] values), chases no node, and allocates
102+
* nothing per lookup. It only applies when the key set is fixed at construction. <code>
103+
* Fixed-map get comparison incl. TagSet + parallel int[] (Apple M1 Max, 8 threads, Java 8 Zulu 8.0.382)
104+
*
105+
* Benchmark Mode Cnt Score Error Units
106+
* UnsynchronizedMapBenchmark.get_hashMap thrpt 6 1086382687.356 ± 52784044.910 ops/s
107+
* UnsynchronizedMapBenchmark.get_hashMap_sameKey thrpt 6 1254600761.612 ± 33664831.344 ops/s
108+
* UnsynchronizedMapBenchmark.get_linkedHashMap thrpt 6 1055047178.664 ± 44355203.950 ops/s
109+
* UnsynchronizedMapBenchmark.get_tagMap thrpt 6 906953688.631 ± 112749682.884 ops/s
110+
* UnsynchronizedMapBenchmark.get_tagMap_sameKey thrpt 6 1055399886.424 ± 162974805.646 ops/s
111+
* UnsynchronizedMapBenchmark.get_tagSetMap thrpt 6 1168134502.026 ± 23922240.061 ops/s
112+
* UnsynchronizedMapBenchmark.get_tagSetMap_sameKey thrpt 6 1379540570.008 ± 15177042.759 ops/s
113+
* UnsynchronizedMapBenchmark.get_treeMap thrpt 6 590656771.941 ± 63177685.351 ops/s
114+
* </code>
94115
*/
95116
@Fork(2)
96117
@Warmup(iterations = 2)
@@ -305,4 +326,39 @@ public void iterate_tagMap_forEach(Blackhole blackhole) {
305326
public TagMap clone_tagMap() {
306327
return TAG_MAP.copy();
307328
}
329+
330+
// TagSet + a parallel value array used as a FIXED (build-once, read-only) map. The keys are known
331+
// up front, so they go in a TagSet and the values in a plain array indexed by the slot that
332+
// Support.indexOf returns. There is no node, no Entry, and no per-lookup allocation -- a get is a
333+
// hash probe (with an interned == fast path) plus one array load. Pull the Data into your own
334+
// static finals (as below) so the hashes/names refs fold to constants -- same static-vs-instance
335+
// win SetBenchmark and KeyOfBenchmark measure. The values live in a parallel int[] -- no boxing,
336+
// the same primitive-value advantage TagMap.getInt has over a HashMap<String, Integer>, whose
337+
// value type structurally forces the box. The trade-off: it cannot change after construction.
338+
static final int[] TAG_SET_HASHES;
339+
static final String[] TAG_SET_NAMES;
340+
static final int[] TAG_SET_VALUES;
341+
342+
static {
343+
TagSet.Data data = TagSet.Support.create(INSERTION_KEYS);
344+
int[] values = new int[data.names.length];
345+
for (int i = 0; i < INSERTION_KEYS.length; ++i) {
346+
values[TagSet.Support.indexOf(data.hashes, data.names, INSERTION_KEYS[i])] = i;
347+
}
348+
TAG_SET_HASHES = data.hashes;
349+
TAG_SET_NAMES = data.names;
350+
TAG_SET_VALUES = values;
351+
}
352+
353+
@Benchmark
354+
public int get_tagSetMap() {
355+
int slot = TagSet.Support.indexOf(TAG_SET_HASHES, TAG_SET_NAMES, nextLookupKey());
356+
return slot < 0 ? -1 : TAG_SET_VALUES[slot];
357+
}
358+
359+
@Benchmark
360+
public int get_tagSetMap_sameKey() {
361+
int slot = TagSet.Support.indexOf(TAG_SET_HASHES, TAG_SET_NAMES, nextLookupKey(INSERTION_KEYS));
362+
return slot < 0 ? -1 : TAG_SET_VALUES[slot];
363+
}
308364
}

0 commit comments

Comments
 (0)