RFR: 8271820: Implementation of JEP 416: Reimplement Core Reflection with Method Handle [v3]

Claes Redestad redestad at openjdk.java.net
Tue Aug 31 21:08:44 UTC 2021


On Fri, 27 Aug 2021 13:12:33 GMT, Maurizio Cimadamore <mcimadamore at openjdk.org> wrote:

>> Mandy Chung has updated the pull request incrementally with one additional commit since the last revision:
>> 
>>   Remove separate accessor for static vs instance method
>>   
>>   There is no effective difference when using MethodHandle::dropArgument for static method.   Removing Static*Accessor and Instance*Accessor simplifies the implementation.
>
> src/java.base/share/classes/jdk/internal/reflect/DirectConstructorAccessorImpl.java line 106:
> 
>> 104:     Object invokeImpl(Object[] args) throws Throwable {
>> 105:         var mhInvoker = mhInvoker();
>> 106:         return switch (paramCount) {
> 
> As @plevart observed, I'm also a bit surprised to see this, but I note your comments regarding performance - especially in cold case. Every adaptation counts, I guess, if you're not in the hot path. But let's make sure that the pay off to add the extra hand-specialized cases is worth it - I'd be surprised if spreading arguments is that expensive.

While I recall it was motivated primarily for startup (note startup numbers for `-Djdk.reflect.useSpreader=true` in @mlchung  reply earlier in this PR), the specialization to avoid the spreader for low-arity arguments also improve performance on throughput microbenchmarks. Surprisingly also reduce the per invocation allocation rate. 

Allocation profiling suggests that with the specialization a varargs array is entirely eliminated. This is of course somewhat fragile and dependent on a number of things - and might ultimately be an artifact of a rather synthetic microbenchmark that will have little benefit in practice, but it's a rather consistent gain for various number of arguments - even when actually going into a spreader (I have some hypotheses as to why this might happen, but most likely we help the JIT to narrow things down for each kind of invocation scheme with the selector):

18 baseline
Benchmark                                                                  Mode  Cnt    Score     Error   Units
ReflectionMethods.static_method                                            avgt   10   57.329 ±   4.217   ns/op
ReflectionMethods.static_method:·gc.alloc.rate.norm                        avgt   10   72.006 ±   0.001    B/op
ReflectionMethods.static_method_3arg                                       avgt   10   70.940 ±   7.457   ns/op
ReflectionMethods.static_method_3arg:·gc.alloc.rate.norm                   avgt   10   96.008 ±   0.002    B/op
ReflectionMethods.static_method_4arg                                       avgt   10   90.453 ±   4.252   ns/op
ReflectionMethods.static_method_4arg:·gc.alloc.rate.norm                   avgt   10  112.010 ±   0.001    B/op

pull/5027
Benchmark                                                                  Mode  Cnt    Score     Error   Units
ReflectionMethods.static_method                                            avgt   10   41.518 ±   2.444   ns/op
ReflectionMethods.static_method:·gc.alloc.rate.norm                        avgt   10   48.004 ±   0.001    B/op
ReflectionMethods.static_method_3arg                                       avgt   10   57.603 ±   3.299   ns/op
ReflectionMethods.static_method_3arg:·gc.alloc.rate.norm                   avgt   10   64.006 ±   0.001    B/op
ReflectionMethods.static_method_4arg                                       avgt   10   56.772 ±   3.971   ns/op
ReflectionMethods.static_method_4arg:·gc.alloc.rate.norm                   avgt   10   80.007 ±   0.001    B/op

On a patch to remove the specialization on top of pull/5027, performance reverts back to numbers very similar to the baseline:

remove-spreader-patch:
Benchmark                                                                  Mode  Cnt    Score     Error   Units
ReflectionMethods.static_method                                            avgt   10   56.644 ±   4.322   ns/op
ReflectionMethods.static_method:·gc.alloc.rate.norm                        avgt   10   72.006 ±   0.001    B/op
ReflectionMethods.static_method_3arg                                       avgt   10   72.353 ±   6.815   ns/op
ReflectionMethods.static_method_3arg:·gc.alloc.rate.norm                   avgt   10   96.008 ±   0.001    B/op
ReflectionMethods.static_method_4arg                                       avgt   10   82.820 ±   8.303   ns/op
ReflectionMethods.static_method_4arg:·gc.alloc.rate.norm                   avgt   10  112.009 ±   0.002    B/op


I think the cold start reduction alone is worth the extra 100 lines or so of code, but if the gain on these microbenchmarks translates to real throughput gains then I think the complexity is more than paid for. Simplifying it while retaining similar characteristics would be great of course, but I think such an exploration should be done as a follow-up.

-------------

PR: https://git.openjdk.java.net/jdk/pull/5027


More information about the core-libs-dev mailing list