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