RFR: 8061950: Class.getMethods() exhibits quadratic time complexity
Peter Levart
peter.levart at gmail.com
Wed Nov 5 16:58:21 UTC 2014
On 11/01/2014 06:49 PM, Peter Levart wrote:
>> On 10/30/2014 07:13 PM, Martin Buchholz wrote:
>> Which is why I was expecting to have to write an adaptive
>> data structure that switches from linear search to hash-based when
>> some threshold is exceeded.
>
> ...or two data structures. Since I have already arranged so that
> construction of MethodTable gets an upper limit on the total number of
> possible Method(s) that can be added to it, we can have two
> MethodTable implementations and switch based on this number:
>
> http://cr.openjdk.java.net/~plevart/jdk9-dev/Class.getMethods/webrev.05/
Hi,
I made some progress on this front. I created new MethodTable
implementation (the third one) with similar performance as HashMap
based, but producing half the garbage. I also added some optimizations
to getMethod(), getMethods() and MethodTable in general that deal with
special cases / parameter types comparison and have measurable impact on
performance (for example loading all rt.jar classes and calling
getMethods() for them):
Original:
19658 classes loaded in 2.003623648 seconds.
494392 methods obtained in 0.932151066 seconds.
494392 methods obtained in 0.968247076 seconds.
494392 methods obtained in 0.989218185 seconds.
494392 methods obtained in 1.018700179 seconds.
494392 methods obtained in 0.945078767 seconds.
494392 methods obtained in 0.930856897 seconds.
494392 methods obtained in 0.905420555 seconds.
494392 methods obtained in 0.89253006 seconds.
494392 methods obtained in 0.914590141 seconds.
494392 methods obtained in 0.891616716 seconds.
494392 methods obtained in 0.891839815 seconds.
494392 methods obtained in 0.892863985 seconds.
494392 methods obtained in 0.960643274 seconds.
494392 methods obtained in 0.959266392 seconds.
494392 methods obtained in 0.894688322 seconds.
494392 methods obtained in 0.892751891 seconds.
494392 methods obtained in 0.912542838 seconds.
494392 methods obtained in 0.912219636 seconds.
494392 methods obtained in 0.895791468 seconds.
494392 methods obtained in 0.891673959 seconds.
Patched:
19658 classes loaded in 2.145270948 seconds.
494398 methods obtained in 0.722630874 seconds.
494398 methods obtained in 0.697521755 seconds.
494398 methods obtained in 0.689642554 seconds.
494398 methods obtained in 0.742724992 seconds.
494398 methods obtained in 0.695693313 seconds.
494398 methods obtained in 0.685169108 seconds.
494398 methods obtained in 0.663012634 seconds.
494398 methods obtained in 0.666446935 seconds.
494398 methods obtained in 0.675662884 seconds.
494398 methods obtained in 0.65369404 seconds.
494398 methods obtained in 0.656608178 seconds.
494398 methods obtained in 0.668384051 seconds.
494398 methods obtained in 0.657548957 seconds.
494398 methods obtained in 0.672332234 seconds.
494398 methods obtained in 0.655699295 seconds.
494398 methods obtained in 0.671819628 seconds.
494398 methods obtained in 0.657232183 seconds.
494398 methods obtained in 0.654462137 seconds.
494398 methods obtained in 0.659630473 seconds.
494398 methods obtained in 0.674219391 seconds.
(the difference in method count is expected - patched code contains new
methods in existing rt.jar classes ;-)
Here's new webrev:
http://cr.openjdk.java.net/~plevart/jdk9-dev/Class.getMethods/webrev.06/
The optimizations made from webrev.05 are:
- getMethod() skips construction of MethodTable if there are no
(super)interfaces.
- getMethods() returns just declared public methods if there are no
superclass and no (super)interfaces.
- comparing method parameter types is optimized by adding two methods to
Method/LangReflectAccess/ReflectionFactory.
New MethodTable implementation based on a linear-probe hash table is a
space/garbage improvement. I took IdentityHashMap, removed unneeded
stuff and modified it's API. The result is a HashArray. It's API is
similar in function and form to java.util.Map, but doesn't use separate
keys and values. An element of HashArray is a key and a value at the
same time. Elements are always non-null, so the method return values are
unambiguous. As HashArray is a linear-probe hash table and there are no
Map.Entry objects involved, the underlying data structure is very simple
and memory efficient. It is just a sparse array of elements with length
that is always a power of two and larger than 3 * size / 2. It also
features overriddable element equals/hashCode methods. I made it a
separate generic class because I think it can find it's usage elsewhere
(for example as a cannonicalizing cache).
Since HashArray based MethodTable is more space-efficient I moved the
line between simple array based and HashArray based MethodTable down to
20 elements to minimize the worst-case scenario effect. Calling
getMethods() on all rt.jar classes now constructs about 3/4 simple array
based and 1/4 HashArray based MethodTables.
Here's also Martin's ManyMethodsBenchmark:
Original:
Base class load time: 129.95 ms
getDeclaredMethods: 65521 methods, 36.58 ms total time, 0.0006 ms per method
getMethods : 65530 methods, 47.43 ms total time, 0.0007 ms per method
Derived class load time: 32216.09 ms
getDeclaredMethods: 65521 methods, 35.05 ms total time, 0.0005 ms per method
getMethods : 65530 methods, 8068.66 ms total time, 0.1231 ms per
method
Patched (using HashArray based MethodTable):
Base class load time: 126.00 ms
getDeclaredMethods: 65521 methods, 36.83 ms total time, 0.0006 ms per method
getMethods : 65530 methods, 45.08 ms total time, 0.0007 ms per method
Derived class load time: 31865.27 ms
getDeclaredMethods: 65521 methods, 35.01 ms total time, 0.0005 ms per method
getMethods : 65530 methods, 78.05 ms total time, 0.0012 ms per method
All 86 jtreg test in java.lang/Class/ and java/lang/reflect/ still pass.
Regards, Peter
More information about the core-libs-dev
mailing list