|
24 | 24 | * <li>TreeMap - better for custom Comparators - case-insensitive Maps (see |
25 | 25 | * CaseInsensitiveMapBenchmark) |
26 | 26 | * <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). |
27 | 31 | * </ul> |
28 | 32 | * |
29 | 33 | * <p>TagMap is the preferred way to store tags. |
|
91 | 95 | * UnsynchronizedMapBenchmark.iterate_tagMap_forEach thrpt 6 422407681.630 ± 19493455.109 ops/s |
92 | 96 | * UnsynchronizedMapBenchmark.iterate_treeMap thrpt 6 392884747.896 ± 80190674.417 ops/s |
93 | 97 | * </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> |
94 | 115 | */ |
95 | 116 | @Fork(2) |
96 | 117 | @Warmup(iterations = 2) |
@@ -305,4 +326,39 @@ public void iterate_tagMap_forEach(Blackhole blackhole) { |
305 | 326 | public TagMap clone_tagMap() { |
306 | 327 | return TAG_MAP.copy(); |
307 | 328 | } |
| 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 | + } |
308 | 364 | } |
0 commit comments