RFR: JDK-8244288 Specialized implementations for putIfAbsent, merge, compute* methods in TreeMap derived maps

Tagir Valeev amaembo at gmail.com
Sun May 3 09:36:30 UTC 2020


Hello!

Please review the following change:
https://bugs.openjdk.java.net/browse/JDK-8244288
http://cr.openjdk.java.net/~tvaleev/webrev/8244288/r1/

We already implemented putIfAbsent, merge, computeIfAbsent,
computeIfPresent, and compute for TreeMap class (see JDK-8176894).
However, default implementations of these methods are still used for
TreeMap derived maps, such as descendingMap(), tailMap(), headMap()
and subMap(). The implementation is pretty straightforward, just a
range-check+delegation for each of these methods inside the
TreeMap.NavigableSubMap (+32 lines in TreeMap.java). Care should be
taken to avoid throwing IAE from compute* methods if the key is
outside of the range but we don't actually add the entry. This mimics
the previous behavior and also consistent with other modification
methods like Map.remove (we don't throw from remove if the key is out
of range, simply do nothing).

To cover these changes with tests, I added new
TreeMap().descendingMap() to
InPlaceOpsCollisions.nullValueFriendlyMaps and
MapWithCollisionsProviders.makeMapsMoreTypes. This map should behave
like a normal Map modulo iteration order but implementation goes
through NavigableSubMap. Also, I added more adders to
LockStep::randomAdder (lines 572+) to cover new methods, as well as
some more throws IAE asserts around line 806. At the same time, I took
the liberty to modernize this test:
- Convert all the raw-types to properly parameterized (using 'var' for
local vars where resulting type is long)
- Convert MapFrobber and SetFrobber to interfaces, and convert all the
implementations to lambdas (automatic conversion via IntelliJ IDEA +
rename of conflicting parameter names)
- Use for-each loop instead of indexed for loop where possible
- 'elts' array was created in two places but unused afterward. I added
size assert to these arrays to use it at least somehow (though
probably content should be checked as well).
- Use Comparator.naturalOrder() instead of manually written one in
comparator() methods (should not affect the testing in any way as it's
only used directly, not passed e.g. to TreeMap constructor).
- Use Objects.equals inside LockStep#equal
- Inverted 'if' at line 299 to avoid empty block.
If the cleanup really complicates the review I can post the cleanup as
a separate changeset. Though it should not be very problematic as the
actual changes are quite compact (as said above, near lines 572 and
806)

I also improved the previously added benchmark TreeMapUpdate to check
descendingMap and tailMap cases to confirm the hypothesis that the
change improves the performance of derived maps. This is indeed the
case. E.g. merge test yields (comparator = false, size = 1000) for
unpatched JDK:

(benchmark)                 (mode) (prefill)    Score      Error  Units
TreeMapUpdate.merge        TreeMap   true   63589,075 ± 1028,065  ns/op
TreeMapUpdate.merge        TreeMap  false   65414,367 ± 1011,442  ns/op
TreeMapUpdate.merge  descendingMap   true  121501,618 ± 1849,108  ns/op
TreeMapUpdate.merge  descendingMap  false   95913,212 ± 2854,063  ns/op
TreeMapUpdate.merge        tailMap   true  126657,309 ± 4467,733  ns/op
TreeMapUpdate.merge        tailMap  false   93448,840 ± 1359,392  ns/op

As you can see, the merge on derived maps is significantly slower.
After the patch is applied the results are much better:

(benchmark)                 (mode)  (prefill)     Score      Error  Units
TreeMapUpdate.merge        TreeMap       true 64059,189 ±  808,230  ns/op
TreeMapUpdate.merge        TreeMap      false 65156,912 ± 1229,965  ns/op
TreeMapUpdate.merge  descendingMap       true 69938,131 ± 1697,357  ns/op
TreeMapUpdate.merge  descendingMap      false 67783,829 ±  263,061  ns/op
TreeMapUpdate.merge        tailMap       true 71079,529 ±  841,738  ns/op
TreeMapUpdate.merge        tailMap      false 68577,169 ± 1196,758  ns/op

I don't think this requires a CSR, as the changed class is
package-private, so it cannot be extended by clients.

With best regards,
Tagir Valeev.


More information about the core-libs-dev mailing list