Hi,

Those two methods were added in JDK 10 and they are not very optimal. Map.copyOf(Map map) 1st dumps the source map into an array of Map.Entry(s) (map.entrySet().toArray(new Entry[0])), which typically creates new Map.Entry objects, then passes the array to Map.ofEntries(Map.Entry[] entries) factory method that iterates the array and constructs a key/value interleaved array from it which is passed to ImmutableCollections.MapN constructor, which then constructs a linear-probing hash table from it. So each key and value is copied 3 times, while several temporary objects are created in the process.

One copying step could be eliminated and construction of temporary Map.Entry objects too:

http://cr.openjdk.java.net/~plevart/jdk-dev/UnmodifiableMap_copyOf/webrev.01/

Collecting stream(s) using Collectors.toUnmodifiableMap() 1st collects key/value pairs into a HashMap, then it performs equivalent operations as Map.copyOf(hashMap) at the end. Using Map.copyOf() directly benefits this collection operation too.

The following benchmark:

http://cr.openjdk.java.net/~plevart/jdk-dev/UnmodifiableMap_copyOf/UnmodifiableMapBench.java


Shows up to 30% improvement of .copyOf operation with this patch applied:

Original:

Benchmark                               (size)  Mode Cnt      Score     Error  Units UnmodifiableMapBench.copyOf                 10  avgt 10    403.633 ±   2.640  ns/op UnmodifiableMapBench.copyOf                100  avgt   10 3489.623 ±  44.590  ns/op UnmodifiableMapBench.copyOf               1000  avgt   10 40030.572 ± 277.075  ns/op UnmodifiableMapBench.toUnmodifiableMap      10  avgt 10    831.221 ±   3.816  ns/op UnmodifiableMapBench.toUnmodifiableMap     100  avgt   10 9783.519 ±  43.097  ns/op UnmodifiableMapBench.toUnmodifiableMap    1000  avgt   10 96524.536 ± 670.818  ns/op

Patched:

Benchmark                               (size)  Mode Cnt      Score      Error  Units UnmodifiableMapBench.copyOf                 10  avgt 10    264.172 ±    1.882  ns/op UnmodifiableMapBench.copyOf                100  avgt   10 2318.974 ±   15.877  ns/op UnmodifiableMapBench.copyOf               1000  avgt   10 29291.782 ± 3139.737  ns/op UnmodifiableMapBench.toUnmodifiableMap      10  avgt 10    771.221 ±   65.432  ns/op UnmodifiableMapBench.toUnmodifiableMap     100  avgt   10 9275.016 ±  725.722  ns/op UnmodifiableMapBench.toUnmodifiableMap    1000  avgt   10 82204.342 ±  851.741  ns/op


Production of garbage is also reduced, since no Map.Entry temporary objects are constructed:

Original:

Benchmark (size)  Mode  Cnt      Score       Error   Units
UnmodifiableMapBench.copyOf:·gc.alloc.rate.norm 10  avgt   10    416.001 ±     0.002    B/op UnmodifiableMapBench.copyOf:·gc.alloc.rate.norm 100  avgt   10   2936.005 ±     0.019    B/op UnmodifiableMapBench.copyOf:·gc.alloc.rate.norm 1000  avgt   10  28136.059 ±     0.199    B/op UnmodifiableMapBench.toUnmodifiableMap:·gc.alloc.rate.norm 10  avgt   10   1368.001 ±     0.004    B/op UnmodifiableMapBench.toUnmodifiableMap:·gc.alloc.rate.norm 100  avgt   10  10208.139 ±     0.045    B/op UnmodifiableMapBench.toUnmodifiableMap:·gc.alloc.rate.norm 1000  avgt   10  93025.923 ±     0.573    B/op

Patched:

Benchmark (size)  Mode  Cnt      Score       Error   Units
UnmodifiableMapBench.copyOf:·gc.alloc.rate.norm 10  avgt   10    304.000 ±     0.001    B/op UnmodifiableMapBench.copyOf:·gc.alloc.rate.norm 100  avgt   10   2464.004 ±     0.012    B/op UnmodifiableMapBench.copyOf:·gc.alloc.rate.norm 1000  avgt   10  24064.040 ±     0.137    B/op UnmodifiableMapBench.toUnmodifiableMap:·gc.alloc.rate.norm 10  avgt   10   1256.001 ±     0.003    B/op UnmodifiableMapBench.toUnmodifiableMap:·gc.alloc.rate.norm 100  avgt   10   9720.153 ±     0.055    B/op UnmodifiableMapBench.toUnmodifiableMap:·gc.alloc.rate.norm 1000  avgt   10  88905.688 ±     0.574    B/op


So what do you think? Is this an improvement?

Regards, Peter

Reply via email to