Skip to content

Commit 0e20fa3

Browse files
dougqhclaude
andcommitted
Benchmark FlatHashtable in the single-threaded map comparison
Adds FlatHashtable to SingleThreadedMapBenchmark on the ops it supports (build via comparison-free insert, get, iterate) — its self-contained entry holds the value unboxed vs HashMap<String,Integer>. Fixed-capacity so the table is sized to the key count. Uses the INSTANCE-singleton strategy style (private ctor, lazy class-init). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent bd24e27 commit 0e20fa3

1 file changed

Lines changed: 69 additions & 0 deletions

File tree

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

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@
3636
* <li>TreeMap — when a custom Comparator is needed (see CaseInsensitiveMapBenchmark)
3737
* <li>LinkedHashMap — only when insertion-order iteration is required; cost is paid at
3838
* construction and in per-entry memory
39+
* <li>FlatHashtable — a find-or-create table over self-contained entries (not a general Map: no
40+
* arbitrary put/remove). Compared here on the ops it does support — build, get, iterate —
41+
* where its self-contained entry stores the value <i>unboxed</i> (one object per key, no
42+
* {@code Integer}). Fixed-capacity, so it must be sized to the working set up front.
3943
* </ul>
4044
*
4145
* <p><b>Uncontended synchronization tax.</b> A {@link Collections#synchronizedMap} case is included
@@ -81,12 +85,54 @@ static TagMap fillTagMap(TagMap map) {
8185
return map;
8286
}
8387

88+
// FlatHashtable is a find-or-create table over self-contained entries — no arbitrary put/remove,
89+
// so only the comparable ops appear here: build (via the comparison-free insert of distinct
90+
// keys),
91+
// get, and iterate. Its entry carries the value UNBOXED (no Integer), one object per key.
92+
static final class IntEntry {
93+
final String key;
94+
final int value;
95+
96+
IntEntry(String key, int value) {
97+
this.key = key;
98+
this.value = value;
99+
}
100+
}
101+
102+
static final class IntEntryKeyStrategy extends FlatHashtable.StringKeyStrategy<IntEntry> {
103+
// Canonical exact-typed singleton: one instance, private ctor => the static-poly discipline is
104+
// enforced by the class, not left to each caller to declare correctly.
105+
static final IntEntryKeyStrategy INSTANCE = new IntEntryKeyStrategy();
106+
107+
private IntEntryKeyStrategy() {}
108+
109+
@Override
110+
public boolean matches(String key, IntEntry entry) {
111+
return key.equals(entry.key);
112+
}
113+
114+
@Override
115+
public long hashOf(IntEntry entry) {
116+
return hash(entry.key);
117+
}
118+
}
119+
120+
static IntEntry[] newFilledFlat() {
121+
// Sized to the key count (FlatHashtable is fixed-capacity, no resize): load factor <= 0.5.
122+
IntEntry[] table = FlatHashtable.create(IntEntry.class, INSERTION_KEYS.length);
123+
for (int i = 0; i < INSERTION_KEYS.length; ++i) {
124+
FlatHashtable.insert(table, new IntEntry(INSERTION_KEYS[i], i), IntEntryKeyStrategy.INSTANCE);
125+
}
126+
return table;
127+
}
128+
84129
// Per-thread prebuilt maps for the read + clone benchmarks (built once per trial, per thread).
85130
HashMap<String, Integer> hashMap;
86131
Map<String, Integer> synchronizedHashMap;
87132
TreeMap<String, Integer> treeMap;
88133
LinkedHashMap<String, Integer> linkedHashMap;
89134
TagMap tagMap;
135+
IntEntry[] flatTable;
90136
int index = 0;
91137

92138
@Setup(Level.Trial)
@@ -99,6 +145,7 @@ public void setUp() {
99145
linkedHashMap = new LinkedHashMap<>();
100146
fill(linkedHashMap);
101147
tagMap = fillTagMap(TagMap.create());
148+
flatTable = newFilledFlat();
102149
}
103150

104151
String nextLookupKey() {
@@ -159,6 +206,11 @@ public TagMap create_tagMap_via_ledger() {
159206
return ledger.build();
160207
}
161208

209+
@Benchmark
210+
public IntEntry[] create_flatHashtable() {
211+
return newFilledFlat();
212+
}
213+
162214
// ---- copy ----
163215

164216
@Benchmark
@@ -200,6 +252,11 @@ public Integer get_synchronizedHashMap() {
200252
return synchronizedHashMap.get(nextLookupKey());
201253
}
202254

255+
@Benchmark
256+
public IntEntry get_flatHashtable() {
257+
return FlatHashtable.get(flatTable, nextLookupKey(), IntEntryKeyStrategy.INSTANCE);
258+
}
259+
203260
@Benchmark
204261
public void iterate_hashMap(Blackhole blackhole) {
205262
for (Map.Entry<String, Integer> entry : hashMap.entrySet()) {
@@ -219,4 +276,16 @@ public void iterate_synchronizedHashMap(Blackhole blackhole) {
219276
}
220277
}
221278
}
279+
280+
@Benchmark
281+
public void iterate_flatHashtable(Blackhole blackhole) {
282+
// Context-passing forEach: blackhole rides through as context, so the lambda doesn't capture.
283+
FlatHashtable.forEach(
284+
flatTable,
285+
blackhole,
286+
(bh, e) -> {
287+
bh.consume(e.key);
288+
bh.consume(e.value);
289+
});
290+
}
222291
}

0 commit comments

Comments
 (0)