On Mon, 13 Apr 2026 19:12:36 GMT, jengebr <[email protected]> wrote:
>> # HashMap.putAll() optimizations
>>
>> ## Summary
>>
>> This PR adds fast paths to `HashMap.putMapEntries()` for `HashMap` and
>> `Collections.UnmodifiableMap(HashMap)` sources. These deliver 66-86%
>> improvement for the targeted cases with zero regression for other map types.
>>
>> ## Problem
>>
>> `HashMap.putMapEntries()` iterates via `entrySet()` using a chain of virtual
>> calls: `entrySet()`, `iterator()`, `hasNext()`, `next()`, `getKey()`,
>> `getValue()` and `hashCode()`. These call sites are inherently megamorphic
>> because `putMapEntries()` accepts any Map implementation. The JIT cannot
>> devirtualize them. `UnmodifiableMap` compounds the problem by adding a
>> megamorphic wrapper layer on top.
>>
>> See JDK-8368292 for full analysis.
>>
>> ## Solution
>>
>> `putMapEntries()` checks whether the source is exactly `HashMap.class` or an
>> `UnmodifiableMap` wrapping one. If so, it dispatches to
>> `putHashMapEntries()` which walks the source `Node[]` table directly. This
>> eliminates the megamorphic iteration call sites and reuses the pre-computed
>> hash stored in each Node — avoiding redundant `hashCode()` calls on every
>> key.
>>
>> An exact class identity check (not `instanceof`) is used for `HashMap` to
>> prevent subclasses from entering the fast path. This avoids correctness
>> issues with overridden behavior and keeps the fast path monomorphic.
>>
>> `UnmodifiableMap.m` visibility is changed from `private` to package-private.
>> The class remains non-public and is only accessible within `java.util`.
>>
>> ## Performance
>>
>> Benchmarks use `BigInteger(128)` keys whose `hashCode()` is not cached. Call
>> sites are poisoned with five Map types to simulate real-world megamorphic
>> conditions. Platform: aarch64 (AWS Graviton).
>>
>> ### Optimized paths (poisoned call sites)
>>
>>
>> Source │Size│Baseline (ns/op)│Optimized (ns/op)│Improvement
>> ─────────────────────────┼────┼────────────────┼─────────────────┼───────────
>> HashMap │ 5 │ 227.3 ± 0.6 │ 78.4 ± 0.3 │ 66%
>> HashMap │ 25 │ 1037.0 ± 22.9│ 286.0 ± 4.0 │ 72%
>> HashMap │150 │ 6654.2 ± 40.9│ 1539.1 ± 27.6 │ 77%
>> UnmodifiableMap(HashMap) │ 5 │ 352.7 ± 0.6 │ 77.5 ± 1.0 │ 78%
>> UnmodifiableMap(HashMap) │ 25 │ 1772.3 ± 5.6 │ 289.6 ± 2.5 │ 84%
>> UnmodifiableMap(HashMap) │150 │ 10593.2 ± 37.5│ 1533.1 ± 20.4 │ 86%
>>
>>
>> ### Non-optimized paths (poisoned, size=150)
>>
>>
>> Source │Baseline (ns/op)│Optimized (ns/op)│Delta
> ...
>
> jengebr has updated the pull request incrementally with two additional
> commits since the last revision:
>
> - Touch2
> - Touch1
src/java.base/share/classes/java/util/HashMap.java line 502:
> 500: @SuppressWarnings("unchecked")
> 501: private void putHashMapEntries(HashMap<? extends K, ? extends V>
> src, boolean evict) {
> 502: Node<K,V>[] tab;
Can use `Node<? extends K, ? extends V>` here to avoid the redundant cast for
`src.table`? The Node type in the loop can be replaced with var.
-------------
PR Review Comment: https://git.openjdk.org/jdk/pull/28243#discussion_r3076836267