Loading classes with many methods is very expensive

Stanimir Simeonoff stanimir at riflexo.com
Sun Oct 26 22:48:14 UTC 2014


On Mon, Oct 27, 2014 at 12:36 AM, Peter Levart <peter.levart at gmail.com>
wrote:

>
> On 10/26/2014 11:21 PM, Stanimir Simeonoff wrote:
>
>  Great effort.
>
> From first glance: the hashCode and equals of MethodList use
> m.getParameterTypes() which is cloned. I'd rather pay the collision costs
> and rely on the default hashCode/equals of Method itself (hashCode ignores
> the parameters). Possibly hashCode can include the returnType but equals
> should be direct call to Method.equals.
>
>
> Can't do that as Method.equals also compares declaringClass, which is not
> part of method signature.
>
> We could use SharedSecrets to access the internal array of parameterTypes
> directly.
>
>
Opps, very true.

If SharedSecrets is not available, get the parameters in the constructor,
so clone happens once only. Method.clone() should be comparable to the cost
of a LHM node, so the overhead is not so high.
I doubt the JIT can EA clone() which would be the best case scenario.

I was wondering if the computation of size is better than just array copy
(as LHM.iterator is effectively linked list), however for small number of
methods it would keep all the references in L1 so that would be better in
the common case.

Stanimir




> Regards, Peter
>
>
>
>  Stanimir
>
>
>
>
> On Sun, Oct 26, 2014 at 10:25 PM, Peter Levart <peter.levart at gmail.com>
> wrote:
>
>> Hi all,
>>
>> I revamped the Class.getMethods() implementation so that it is faster for
>> common cases and O(n) at the same time, which makes Martin's test happy (at
>> least in part that measures getMethods() speed - the class loading /
>> linkage in VM is a separate issue).
>>
>> With the following test that loads all classes from rt.jar and calls
>> getMethods() on each of them:
>>
>>
>> http://cr.openjdk.java.net/~plevart/jdk9-dev/Class.getMethods/GetAllRtMethods.java
>>
>> And system property 'sun.reflect.noCaches=true' (so that we exercise the
>> logic in every loop - not just 1st), original code prints:
>>
>> 19657 classes loaded in 1.987373401 seconds.
>> 494141 methods obtained in 1.02493941 seconds.
>> 494141 methods obtained in 0.905235658 seconds.
>> 494141 methods obtained in 0.914434303 seconds.
>> 494141 methods obtained in 0.887212805 seconds.
>> 494141 methods obtained in 0.888929483 seconds.
>> 494141 methods obtained in 0.883309141 seconds.
>> 494141 methods obtained in 0.88341098 seconds.
>> 494141 methods obtained in 0.897397146 seconds.
>> 494141 methods obtained in 0.885677466 seconds.
>> 494141 methods obtained in 0.895834176 seconds.
>>
>> Patched code does the same about 10% faster:
>>
>> 19657 classes loaded in 2.084409717 seconds.
>> 494124 methods obtained in 0.915928578 seconds.
>> 494124 methods obtained in 0.785342465 seconds.
>> 494124 methods obtained in 0.784852619 seconds.
>> 494124 methods obtained in 0.793450205 seconds.
>> 494124 methods obtained in 0.849915078 seconds.
>> 494124 methods obtained in 0.77835511 seconds.
>> 494124 methods obtained in 0.764144701 seconds.
>> 494124 methods obtained in 0.754122383 seconds.
>> 494124 methods obtained in 0.747961897 seconds.
>> 494124 methods obtained in 0.7489937 seconds.
>>
>> Martin's test prints on my computer with original code the following:
>>
>> Base class load time: 177.80 ms
>> getDeclaredMethods: Methods: 65521, Total time: 35.79 ms, Time per
>> method: 0.0005 ms
>> getMethods        : Methods: 65530, Total time: 50.15 ms, Time per
>> method: 0.0008 ms
>> Derived class load time: 34015.70 ms
>> getDeclaredMethods: Methods: 65521, Total time: 33.82 ms, Time per
>> method: 0.0005 ms
>> getMethods        : Methods: 65530, Total time: 8122.00 ms, Time per
>> method: 0.1239 ms
>>
>> And with patched code this:
>>
>> Base class load time: 157.16 ms
>> getDeclaredMethods: Methods: 65521, Total time: 65.77 ms, Time per
>> method: 0.0010 ms
>> getMethods        : Methods: 65530, Total time: 44.64 ms, Time per
>> method: 0.0007 ms
>> Derived class load time: 33996.69 ms
>> getDeclaredMethods: Methods: 65521, Total time: 32.63 ms, Time per
>> method: 0.0005 ms
>> getMethods        : Methods: 65530, Total time: 92.12 ms, Time per
>> method: 0.0014 ms
>>
>>
>> Here's a webrev of the patch:
>>
>> http://cr.openjdk.java.net/~plevart/jdk9-dev/Class.getMethods/webrev.01/
>>
>> Patched code is simpler (65 lines gone) and I hope, easier to understand
>> and change (I think a change in spec is coming in JDK9 which will handle
>> abstract interface methods the same way as default, right Joel?)
>>
>> I also took the liberty to eliminate some redundant array and
>> Field/Method/Constructor copies. get[Method0,Field0,Counstuctor0] now
>> return 'root' objects. Copying is performed in methods that call them and
>> expose the objects to any code outside java.lang.Class. Also, findFields()
>> and findMethods() don't do copying of Field/Method objects themselves, but
>> rather live that to methods that call them. getInterfaces() method now
>> delegates to getInterfaces(boolean copyArray) so that internally, not array
>> copying is performed.
>>
>> All 55 java/lang/reflect jtreg tests pass with this patch.
>>
>>
>> Regards, Peter
>>
>>
>
>


More information about the hotspot-runtime-dev mailing list