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

Mandy Chung mchung at openjdk.java.net
Sat Aug 21 22:56:29 UTC 2021


On Sat, 21 Aug 2021 22:37:05 GMT, Mandy Chung <mchung at openjdk.org> wrote:

>> This reimplements core reflection with method handles.
>> 
>> For `Constructor::newInstance` and `Method::invoke`, the new implementation uses `MethodHandle`.  For `Field` accessor, the new implementation uses `VarHandle`.    For the first few invocations of one of these reflective methods on a specific reflective object we invoke the corresponding method handle directly. After that we spin a dynamic bytecode stub defined in a hidden class which loads the target `MethodHandle` or `VarHandle` from its class data as a dynamically computed constant. Loading the method handle from a constant allows JIT to inline the method-handle invocation in order to achieve good performance.
>> 
>> The VM's native reflection methods are needed during early startup, before the method-handle mechanism is initialized. That happens soon after System::initPhase1 and before System::initPhase2, after which we switch to using method handles exclusively.
>> 
>> The core reflection and method handle implementation are updated to handle chained caller-sensitive method calls [1] properly. A caller-sensitive method can define with a caller-sensitive adapter method that will take an additional caller class parameter and the adapter method will be annotated with `@CallerSensitiveAdapter` for better auditing.   See the detailed description from [2].
>> 
>> Ran tier1-tier8 tests.   
>> 
>> [1] https://bugs.openjdk.java.net/browse/JDK-8013527
>> [2] https://bugs.openjdk.java.net/browse/JDK-8271820?focusedCommentId=14439430&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-14439430
>
> 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.

Peter,

I have a patch [1] that adds `-Djdk.reflect.useSpreader` and `-Djdk.reflect.useCatchExceptionCombinator`
flags for doing performance comparison using `asSpreader` without specializing
methods with <= 3 argument and using the `catchException` combinator.    Note
that [1] does not change the exception handling code as I keep it simple just for
performance discussion.

The main drive of the specialization and not using `catchException` is the 
cold startup regression.   Note that we run `ReflectionColdstartBenchmark`
without JMH.  Running it with JMH may overly skew results.  I increase the
number running `ReflectionColdstartBenchmark` with JMH and without JMH
below.

The use of `asSpreader` increases the cold startup time by ~5ms, from 98ms
to 103ms whereas the use of `catchException` increases the cold startup time
by ~17ms, from 98ms to ~115ms.

For the instance_method* and static_method* benchmarks, we need to look at the
disassembly and understand where the performance differences come from.

One idea we'd like to explore is to have a special decoding logic in the spreader
that can wrap NPE and CCE with IAE for core reflection use.  This will remove
the need of decoding the exception's stacktrace and using the `catchException`
combinator.  Core reflection can use the spreader with a special decoding logic.

I'd propose the current implementation as the initial integration for this JEP
and explore the spreader special logic and other incremental performance
improvement in the core method handle implementation like in spreader or
`catchException` combinator after integration.  What do you think?

[1] http://cr.openjdk.java.net/~mchung/jep416/useSpreader-catchException-combinator.patch


-Djdk.reflect.useSpreader=false -Djdk.reflect.useCatchExceptionCombinator=false

ReflectionMethods.instance_method           avgt   10    15.419 ?  0.026  ns/op
ReflectionMethods.instance_method_3arg      avgt   10    19.991 ?  0.087  ns/op
ReflectionMethods.instance_method_var       avgt   10    15.325 ?  0.048  ns/op
ReflectionMethods.instance_method_var_3arg  avgt   10    22.998 ?  0.093  ns/op
ReflectionMethods.static_method             avgt   10    13.927 ?  0.038  ns/op
ReflectionMethods.static_method_3arg        avgt   10    18.040 ?  0.081  ns/op
ReflectionMethods.static_method_var         avgt   10    14.165 ?  0.048  ns/op
ReflectionMethods.static_method_var_3arg    avgt   10    17.573 ?  0.160  ns/op
ReflectionColdstartBenchmark.invokeMethods    ss   10  2096.975 ? 90.331  us/op

-Djdk.reflect.useSpreader=true -Djdk.reflect.useCatchExceptionCombinator=false

ReflectionMethods.instance_method           avgt   10    23.217 ?   0.058  ns/op
ReflectionMethods.instance_method_3arg      avgt   10    27.676 ?   0.223  ns/op
ReflectionMethods.instance_method_var       avgt   10    25.428 ?   0.038  ns/op
ReflectionMethods.instance_method_var_3arg  avgt   10    30.340 ?   0.079  ns/op
ReflectionMethods.static_method             avgt   10    21.705 ?   0.046  ns/op
ReflectionMethods.static_method_3arg        avgt   10    26.003 ?   0.039  ns/op
ReflectionMethods.static_method_var         avgt   10    24.916 ?   0.040  ns/op
ReflectionMethods.static_method_var_3arg    avgt   10    28.269 ?   0.067  ns/op
ReflectionColdstartBenchmark.invokeMethods    ss   10  3328.333 ? 882.301  us/op

-Djdk.reflect.useSpreader=false -Djdk.reflect.useCatchExceptionCombinator=true

ReflectionMethods.instance_method           avgt   10    15.416 ?   0.043  ns/op
ReflectionMethods.instance_method_3arg      avgt   10    20.150 ?   0.161  ns/op
ReflectionMethods.instance_method_var       avgt   10    15.430 ?   0.094  ns/op
ReflectionMethods.instance_method_var_3arg  avgt   10    21.688 ?   0.105  ns/op
ReflectionMethods.static_method             avgt   10    13.894 ?   0.026  ns/op
ReflectionMethods.static_method_3arg        avgt   10    19.340 ?   0.054  ns/op
ReflectionMethods.static_method_var         avgt   10    14.250 ?   0.039  ns/op
ReflectionMethods.static_method_var_3arg    avgt   10    17.573 ?   0.130  ns/op
ReflectionColdstartBenchmark.invokeMethods    ss   10  3947.061 ? 171.567  us/op

-Djdk.reflect.useSpreader=true -Djdk.reflect.useCatchExceptionCombinator=true

ReflectionMethods.instance_method           avgt   10    22.760 ?    0.049  ns/op
ReflectionMethods.instance_method_3arg      avgt   10    27.855 ?    0.143  ns/op
ReflectionMethods.instance_method_var       avgt   10    25.730 ?    0.106  ns/op
ReflectionMethods.instance_method_var_3arg  avgt   10    31.697 ?    5.826  ns/op
ReflectionMethods.static_method             avgt   10    21.682 ?    0.070  ns/op
ReflectionMethods.static_method_3arg        avgt   10    25.714 ?    0.151  ns/op
ReflectionMethods.static_method_var         avgt   10    24.397 ?    0.047  ns/op
ReflectionMethods.static_method_var_3arg    avgt   10    27.758 ?    0.154  ns/op
ReflectionColdstartBenchmark.invokeMethods    ss   10  5103.479 ? 2006.719  us/op

 Performance counter stats for 'build/linux-x86_64-server-release/images/jdk/bin/java -Djdk.reflect.useSpreader=false -Djdk.refle
ct.useCatchExceptionCombinator=false -cp build/linux-x86_64-server-release/images/test/micro/benchmarks.jar org.openjdk.bench.jav
a.lang.reflect.ReflectionColdstartBenchmark' (50 runs):

        156.098457      task-clock:u (msec)       #    1.594 CPUs utilized            ( +-  1.10% )
                 0      context-switches:u        #    0.000 K/sec
                 0      cpu-migrations:u          #    0.000 K/sec
              4135      page-faults:u             #    0.026 M/sec                    ( +-  0.12% )
         187098445      cycles:u                  #    1.199 GHz                      ( +-  1.26% )
         204415894      instructions:u            #    1.09  insn per cycle           ( +-  0.41% )
          38603060      branches:u                #  247.299 M/sec                    ( +-  0.44% )
           1396828      branch-misses:u           #    3.62% of all branches          ( +-  1.21% )

       0.097899139 seconds time elapsed                                          ( +-  1.31% )


 Performance counter stats for 'build/linux-x86_64-server-release/images/jdk/bin/java -Djdk.reflect.useSpreader=true -Djdk.reflec
t.useCatchExceptionCombinator=false -cp build/linux-x86_64-server-release/images/test/micro/benchmarks.jar org.openjdk.bench.java
.lang.reflect.ReflectionColdstartBenchmark' (50 runs):

        172.039531      task-clock:u (msec)       #    1.668 CPUs utilized            ( +-  0.78% )
                 0      context-switches:u        #    0.000 K/sec
                 0      cpu-migrations:u          #    0.000 K/sec
              4168      page-faults:u             #    0.024 M/sec                    ( +-  0.09% )
         216832246      cycles:u                  #    1.260 GHz                      ( +-  0.82% )
         231306186      instructions:u            #    1.07  insn per cycle           ( +-  0.34% )
          43927129      branches:u                #  255.332 M/sec                    ( +-  0.37% )
           1740904      branch-misses:u           #    3.96% of all branches          ( +-  0.74% )

       0.103161477 seconds time elapsed                                          ( +-  0.86% )


 Performance counter stats for 'build/linux-x86_64-server-release/images/jdk/bin/java -Djdk.reflect.useSpreader=false -Djdk.reflect.useCatchExceptionCombinator=true -cp build/linux-x86_64-server-release/images/test/micro/benchmarks.jar org.openjdk.bench.java.lang.reflect.ReflectionColdstartBenchmark' (50 runs):

        197.458370      task-clock:u (msec)       #    1.724 CPUs utilized            ( +-  1.09% )
                 0      context-switches:u        #    0.000 K/sec
                 0      cpu-migrations:u          #    0.000 K/sec
              4407      page-faults:u             #    0.022 M/sec                    ( +-  0.10% )
         252564982      cycles:u                  #    1.279 GHz                      ( +-  1.07% )
         261908793      instructions:u            #    1.04  insn per cycle           ( +-  0.35% )
          50031099      branches:u                #  253.375 M/sec                    ( +-  0.37% )
           2094636      branch-misses:u           #    4.19% of all branches          ( +-  1.09% )

       0.114564702 seconds time elapsed                                          ( +-  1.07% )

 Performance counter stats for 'build/linux-x86_64-server-release/images/jdk/bin/java -Djdk.reflect.useSpreader=true -Djdk.reflect.useCatchExceptionCombinator=true -cp build/linux-x86_64-server-release/images/test/micro/benchmarks.jar org.openjdk.bench.java.lang.reflect.ReflectionColdstartBenchmark' (50 runs):

        202.628008      task-clock:u (msec)       #    1.738 CPUs utilized            ( +-  0.99% )
                 0      context-switches:u        #    0.000 K/sec
                 0      cpu-migrations:u          #    0.000 K/sec
              4402      page-faults:u             #    0.022 M/sec                    ( +-  0.10% )
         258306496      cycles:u                  #    1.275 GHz                      ( +-  1.14% )
         267584861      instructions:u            #    1.04  insn per cycle           ( +-  0.38% )
          51154598      branches:u                #  252.456 M/sec                    ( +-  0.40% )
           2174735      branch-misses:u           #    4.25% of all branches          ( +-  1.18% )

       0.116577823 seconds time elapsed                                          ( +-  0.82% )

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

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


More information about the core-libs-dev mailing list