Skip to content

Commit f92aab4

Browse files
dougqhclaude
andcommitted
Benchmark FlatHashtable's lock-free read in the thread-safe map comparison
Adds FlatHashtable to ThreadSafeMapBenchmark: build + concurrent get on a shared, once-published table (lock-free, no volatile) alongside ConcurrentHashMap / volatile-HashMap / synchronizedHashMap. Fixture mirrors SingleThreadedMapBenchmark (self-contained per benchmark). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent fb84b62 commit f92aab4

1 file changed

Lines changed: 55 additions & 0 deletions

File tree

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

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
* <li>ConcurrentMap - only when there are simultaneously readers & writers in multiple threads
2222
* <li>HashMap via volatile - preferred for background thread updates
2323
* <li>synchronized HashMap - when simultaneous readers & writers are uncommon (e.g. tags)
24+
* <li>FlatHashtable - lock-free reads (no lock, no volatile; benign-race) of a fixed, once-built
25+
* keyed set; a find-or-create table, not a general concurrent Map (no arbitrary put/remove)
2426
* </ul>
2527
*
2628
* <p>
@@ -104,6 +106,46 @@ static void fill(Map<String, Integer> map) {
104106
}
105107
}
106108

109+
// FlatHashtable's contribution here is the lock-free concurrent read: get() is a plain array
110+
// probe
111+
// with no lock and no volatile — safe under concurrency because the table is published once (a
112+
// final static field) and each entry's identity fields are final. (Fixture mirrors the one in
113+
// SingleThreadedMapBenchmark; the benchmarks are self-contained.)
114+
static final class IntEntry {
115+
final String key;
116+
final int value;
117+
118+
IntEntry(String key, int value) {
119+
this.key = key;
120+
this.value = value;
121+
}
122+
}
123+
124+
static final class IntEntryKeyStrategy extends FlatHashtable.StringKeyStrategy<IntEntry> {
125+
static final IntEntryKeyStrategy INSTANCE = new IntEntryKeyStrategy();
126+
127+
private IntEntryKeyStrategy() {}
128+
129+
@Override
130+
public boolean matches(String key, IntEntry entry) {
131+
return key.equals(entry.key);
132+
}
133+
134+
@Override
135+
public long hashOf(IntEntry entry) {
136+
return hash(entry.key);
137+
}
138+
}
139+
140+
static IntEntry[] _create_flat() {
141+
// Sized to the key count (FlatHashtable is fixed-capacity, no resize): load factor <= 0.5.
142+
IntEntry[] table = FlatHashtable.create(IntEntry.class, INSERTION_KEYS.length);
143+
for (int i = 0; i < INSERTION_KEYS.length; ++i) {
144+
FlatHashtable.insert(table, new IntEntry(INSERTION_KEYS[i], i), IntEntryKeyStrategy.INSTANCE);
145+
}
146+
return table;
147+
}
148+
107149
static final HashMap<String, Integer> _create_hashMap() {
108150
HashMap<String, Integer> map = new HashMap<>();
109151
fill(map);
@@ -177,4 +219,17 @@ public ConcurrentSkipListMap<String, Integer> create_concSkipListMap() {
177219
public Integer get_concSkipListMap() {
178220
return CONC_SKIP_LIST_MAP.get(nextLookupKey());
179221
}
222+
223+
@Benchmark
224+
public IntEntry[] create_flatHashtable() {
225+
return _create_flat();
226+
}
227+
228+
static final IntEntry[] FLAT_TABLE = _create_flat();
229+
230+
@Benchmark
231+
public IntEntry get_flatHashtable() {
232+
// Lock-free concurrent read of the shared, once-published table.
233+
return FlatHashtable.get(FLAT_TABLE, nextLookupKey(), IntEntryKeyStrategy.INSTANCE);
234+
}
180235
}

0 commit comments

Comments
 (0)