On Wed, 11 Feb 2026 15:28:56 GMT, jengebr <[email protected]> wrote: >> # HashMap.putAll() optimizations: Eliminating Megamorphic Call Site >> Bottlenecks >> >> ## Summary >> >> This PR addresses performance bottlenecks in `HashMap.putMapEntries()` by >> implementing direct optimizations for specific input types: `j.u.HashMap` >> and `j.u.Collections$UnmodifiableMap`. The optimizations target >> `HashMap(Map)` constructor and `putAll()` operations based on the real-world >> megamorphic behavior identified in >> [JDK-8368292](https://bugs.openjdk.org/browse/JDK-8368292), delivering >> significant performance improvements when multiple `Map` subtypes are used. >> >> ## Problem Context >> >> ### Megamorphic Call Site Overhead in Map Iteration >> `HashMap.putMapEntries()` currently uses a generic approach that suffers >> from megamorphic call site overhead when applications perform bulk creation >> or population of HashMaps from various source map types: >> >> 1. `m.entrySet()` becomes megamorphic across different map implementations >> 2. `entrySet().iterator()` creates different iterator types >> 3. `entry.getKey()` and `entry.getValue()` calls vary by map type >> 4. Individual `putVal()` calls for each entry >> >> When the source is `Collections$UnmodifiableMap`, the problem is compounded >> by megamorphic wrappers around the already-megamorphic iteration methods. In >> cases where the unwrapped map is also a HashMap, both the wrapper overhead >> and the iteration overhead can be eliminated with a single optimization. >> >> ## Optimized Methods >> >> ### HashMap >> - **`putMapEntries(Map<? extends K, ? extends V> m, boolean evict)`**: Added >> fast paths for UnmodifiableMap unwrapping and HashMap-to-HashMap copying >> - **`putMapEntries(HashMap<? extends K, ? extends V> src, boolean evict)`**: >> copies HashMap-to-HashMap via direct Node processing. Avoids polymorphic >> issues and eliminates redundant calls to HashMap.hash(). >> >> ## Implementation Details >> >> ### HashMap-to-HashMap Fast Path >> Eliminates megamorphic iteration by targeting internal Node structure - and >> also reuses the pre-calculated hash code, thus avoiding megamorphic calls to >> Object.hashCode() and the sometimes-expensive recalculation (depending on >> key type). This also eliminates direct reads from the key, thus reducing >> the set of objects accessed. >> >> ### UnmodifiableMap Unwrapping >> Detects UnmodifiableMap instances and accesses the underlying map directly >> via the `m` field, eliminating wrapper-induced megamorphic call sites. >> UnmodifiableMap visibility changed from `private` to package-private to >> enable this... > > jengebr has updated the pull request incrementally with one additional commit > since the last revision: > > Addressing CR feedback (formatting + safe casts)
Thank you to everyone who has reviewed this! Here's where I'm at: 1. Excellent feedback all around, thanks. 2. I will fully incorporate the suggestions around comments, using HashMap's internal style, etc. 3. I will use the identity check for HashMap.class in order to a) avoid any future concerns about class overrides, b) guarantee monomorphic execution, c) eliminate any hidden costs from navigating the class hierarchy - Eclipse's UnifiedMap has a 5+ deep structure and would only fail the test anyway. No blocking disagreements, and I'll implement these shortly. Discussion points: 1. Unwrapping UnmodifiableMap for non-HashMap cases such as UnmodifiableMap(TreeMap) improves their performance by 40%, according to the benchmark data in the current PR description. This was a side effect of fixing my target case and not my primary goal. Do we want to lose that in favor of code proximity? I can live with any outcome here. 2. This makes me squirm: > Also, we've been advocating for a long time (with varied success) that > objects protect encapsulation by wrapping internal collections with an > unmodifiable wrapper Unmodifiable* methods are globally megamorphic, so adding these wrappers not only eliminates the sole workaround to JDK-8368292, but also creates cases where the callsite of Unmodifiable* may be megamorphic and the called method is trivial followed by yet another megamorphic call. Also, one of our standard ways to reduce garbage collection is to eliminate the allocation of trivial wrappers such as these. From a performance perspective, this advice is clearly negative. I acknowledge the non-performance benefits. ------------- PR Comment: https://git.openjdk.org/jdk/pull/28243#issuecomment-4206318990
