-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Description
Description
HashMap.<init>(Map)
is slow because it contains polymorphic calls to Map.entrySet(
), Set.iterator()
, Iterator.hasNext()
, and Iterator.next()
. These calls are megamorphic and the JIT cannot optimize them away, so the virtual method calls are quite expensive. I have attached a JMH test that demonstrates this general problem.
Profiling of multiple large, efficiency-critical applications shows that log4j contains one instance of this that is a hotspot in all of the applications: DefaultThreadContextMap.getCopy()
. The call path is CloseableThreadContext$Instance.putAll()
-> org.apache.logging.log4j.ThreadContext.getContext()
-> org.apache.logging.log4j.spi.DefaultThreadContextMap.getCopy()
. The line of code is return new HashMap<>(getMap(state));
.
This can be trivially improved by unrolling the loop:
Map<String, String> map = getMap(state);
HashMap<String, String> copy = new HashMap<>((int)(map.size() * 1.35));
for (Map.Entry<String, String> entry : map.entrySet()) {
copy.put(entry.getKey(), entry.getValue());
}
return copy;
Benchmark before:
Benchmark (mapSize) Mode Cnt Score Error Units
DefaultThreadContextMapCopyBenchmark.copyMap 5 avgt 5 271.915 ± 3.054 ns/op
Benchmark after:
Benchmark (mapSize) Mode Cnt Score Error Units
DefaultThreadContextMapCopyBenchmark.copyMap 5 avgt 5 129.784 ± 3.412 ns/op
Configuration
Logs
Reproduction
Two JMH tests attached, one showing the general effect and one demonstrating this opportunity.
DefaultThreadContextMapCopyBenchmark.java
Metadata
Metadata
Assignees
Labels
Type
Projects
Status