|
21 | 21 | * <ul> |
22 | 22 | * Benchmark comparing different Map-s... |
23 | 23 | * <li>(RECOMMENDED) HashMap - fastest lookups among general-purpose (mutable) maps - (not |
24 | | - * typically needed for tags; a fixed TagSet map below is faster still when the keys are |
| 24 | + * typically needed for tags; a fixed StringIndex map below is faster still when the keys are |
25 | 25 | * known) |
26 | 26 | * <li>(RECOMMENDED) TagMap - for storing tags - especially if copying between maps or using |
27 | 27 | * builders |
28 | 28 | * <li>TreeMap - better for custom Comparators - case-insensitive Maps (see |
29 | 29 | * CaseInsensitiveMapBenchmark) |
30 | 30 | * <li>LinkedHashMap - only when insertion order is needed |
31 | | - * <li>TagSet + parallel value array - for a FIXED (build-once, read-only) map whose key set is |
32 | | - * known up front: the keys go in a {@link TagSet} and the values in a parallel array indexed |
33 | | - * by the slot {@code indexOf} returns. Fastest get, no per-lookup allocation, no node chasing |
34 | | - * - but it can't change after construction (see get_tagSetMap). |
| 31 | + * <li>StringIndex + parallel value array - for a FIXED (build-once, read-only) map whose key set |
| 32 | + * is known up front: the keys go in a {@link StringIndex} and the values in a parallel array |
| 33 | + * indexed by the slot {@code indexOf} returns. Fastest get, no per-lookup allocation, no node |
| 34 | + * chasing - but it can't change after construction (see get_stringIndexMap). |
35 | 35 | * </ul> |
36 | 36 | * |
37 | 37 | * <p>TagMap is the preferred way to store tags. |
|
51 | 51 | * <p>HashMap & TagMap also perform exceedingly well in cases where the exact same object is used |
52 | 52 | * for put & get operations. e.g. when using String literals or Class literals as keys. |
53 | 53 | * |
54 | | - * <p>A TagSet + parallel int[] used as a fixed (build-once) map is the fastest get here -- ~30% |
55 | | - * ahead of HashMap on the rotating-key path and ~50% ahead on the same-key path, where it sustains |
56 | | - * 5.4B ops/s at the tightest error in the table (±1.3%). It pays no boxing (int[] values), chases |
57 | | - * no node, and allocates nothing per lookup. It only applies when the key set is fixed at |
| 54 | + * <p>A StringIndex + parallel int[] used as a fixed (build-once) map is the fastest get here -- |
| 55 | + * ~30% ahead of HashMap on the rotating-key path and ~50% ahead on the same-key path, where it |
| 56 | + * sustains 5.4B ops/s at the tightest error in the table (±1.3%). It pays no boxing (int[] values), |
| 57 | + * chases no node, and allocates nothing per lookup. It only applies when the key set is fixed at |
58 | 58 | * construction. <code> |
59 | 59 | * Apple M1 Max (10 core), macOS 26.4.1 -- 8 threads (per-thread state), 2 forks -- Java 17 (Zulu 17.42.19, 17.0.7+7-LTS) |
60 | 60 | * |
|
76 | 76 | * UnsynchronizedMapBenchmark.get_linkedHashMap thrpt 6 1871397460.306 ± 51848940.996 ops/s |
77 | 77 | * UnsynchronizedMapBenchmark.get_tagMap thrpt 6 1706422514.145 ± 91472057.777 ops/s |
78 | 78 | * UnsynchronizedMapBenchmark.get_tagMap_sameKey thrpt 6 2205821374.441 ± 108659512.329 ops/s |
79 | | - * UnsynchronizedMapBenchmark.get_tagSetMap thrpt 6 2448917198.752 ± 105399021.596 ops/s |
80 | | - * UnsynchronizedMapBenchmark.get_tagSetMap_sameKey thrpt 6 5358887465.195 ± 70196881.552 ops/s |
| 79 | + * UnsynchronizedMapBenchmark.get_stringIndexMap thrpt 6 2448917198.752 ± 105399021.596 ops/s |
| 80 | + * UnsynchronizedMapBenchmark.get_stringIndexMap_sameKey thrpt 6 5358887465.195 ± 70196881.552 ops/s |
81 | 81 | * UnsynchronizedMapBenchmark.get_treeMap thrpt 6 704782343.575 ± 52129796.457 ops/s |
82 | 82 | * |
83 | 83 | * UnsynchronizedMapBenchmark.iterate_hashMap thrpt 6 132215205.201 ± 9079485.505 ops/s |
@@ -303,38 +303,42 @@ public TagMap clone_tagMap() { |
303 | 303 | return TAG_MAP.copy(); |
304 | 304 | } |
305 | 305 |
|
306 | | - // TagSet + a parallel value array used as a FIXED (build-once, read-only) map. The keys are known |
307 | | - // up front, so they go in a TagSet and the values in a plain array indexed by the slot that |
| 306 | + // StringIndex + a parallel value array used as a FIXED (build-once, read-only) map. The keys are |
| 307 | + // known |
| 308 | + // up front, so they go in a StringIndex and the values in a plain array indexed by the slot that |
308 | 309 | // Support.indexOf returns. There is no node, no Entry, and no per-lookup allocation -- a get is a |
309 | 310 | // hash probe (with an interned == fast path) plus one array load. Pull the Data into your own |
310 | 311 | // static finals (as below) so the hashes/names refs fold to constants -- same static-vs-instance |
311 | 312 | // win SetBenchmark and KeyOfBenchmark measure. The values live in a parallel int[] -- no boxing, |
312 | 313 | // the same primitive-value advantage TagMap.getInt has over a HashMap<String, Integer>, whose |
313 | 314 | // value type structurally forces the box. The trade-off: it cannot change after construction. |
314 | | - static final int[] TAG_SET_HASHES; |
315 | | - static final String[] TAG_SET_NAMES; |
316 | | - static final int[] TAG_SET_VALUES; |
| 315 | + static final int[] STRING_INDEX_HASHES; |
| 316 | + static final String[] STRING_INDEX_NAMES; |
| 317 | + static final int[] STRING_INDEX_VALUES; |
317 | 318 |
|
318 | 319 | static { |
319 | | - TagSet.Data data = TagSet.Support.create(INSERTION_KEYS); |
| 320 | + StringIndex.Data data = StringIndex.Support.create(INSERTION_KEYS); |
320 | 321 | int[] values = new int[data.names.length]; |
321 | 322 | for (int i = 0; i < INSERTION_KEYS.length; ++i) { |
322 | | - values[TagSet.Support.indexOf(data.hashes, data.names, INSERTION_KEYS[i])] = i; |
| 323 | + values[StringIndex.Support.indexOf(data.hashes, data.names, INSERTION_KEYS[i])] = i; |
323 | 324 | } |
324 | | - TAG_SET_HASHES = data.hashes; |
325 | | - TAG_SET_NAMES = data.names; |
326 | | - TAG_SET_VALUES = values; |
| 325 | + STRING_INDEX_HASHES = data.hashes; |
| 326 | + STRING_INDEX_NAMES = data.names; |
| 327 | + STRING_INDEX_VALUES = values; |
327 | 328 | } |
328 | 329 |
|
329 | 330 | @Benchmark |
330 | | - public int get_tagSetMap() { |
331 | | - int slot = TagSet.Support.indexOf(TAG_SET_HASHES, TAG_SET_NAMES, nextLookupKey()); |
332 | | - return slot < 0 ? -1 : TAG_SET_VALUES[slot]; |
| 331 | + public int get_stringIndexMap() { |
| 332 | + int slot = |
| 333 | + StringIndex.Support.indexOf(STRING_INDEX_HASHES, STRING_INDEX_NAMES, nextLookupKey()); |
| 334 | + return slot < 0 ? -1 : STRING_INDEX_VALUES[slot]; |
333 | 335 | } |
334 | 336 |
|
335 | 337 | @Benchmark |
336 | | - public int get_tagSetMap_sameKey() { |
337 | | - int slot = TagSet.Support.indexOf(TAG_SET_HASHES, TAG_SET_NAMES, nextLookupKey(INSERTION_KEYS)); |
338 | | - return slot < 0 ? -1 : TAG_SET_VALUES[slot]; |
| 338 | + public int get_stringIndexMap_sameKey() { |
| 339 | + int slot = |
| 340 | + StringIndex.Support.indexOf( |
| 341 | + STRING_INDEX_HASHES, STRING_INDEX_NAMES, nextLookupKey(INSERTION_KEYS)); |
| 342 | + return slot < 0 ? -1 : STRING_INDEX_VALUES[slot]; |
339 | 343 | } |
340 | 344 | } |
0 commit comments