Feature #11004
Optimizing Java Reflection Method.invoke
0%
Related issues
History
#1 Updated by Teodor Gorghe 7 months ago
I have done some testing to benchmarks to conclude some testing which includes Java Reflection invocation scenarios and I have come with the following conclusion:
- For a 4GL internal procedure, when called 100000 times, it takes 72 milliseconds. For the same scenario in FWD, the time goes down to 50 milliseconds.
- When calling external procedures 1000 times, 4GL takes 352 ms and FWD takes around 8 ms.
Even that this shows that there are no issues, there are some scenarios when FWD uses reflection method invocation for stuff like static dataset proxy, field reference, etc, which these scenarios are slower than 4GL.
I have done some tests with Java 17 vs Java 21 and Java 25, having FWD compiled with Java 17/21/25 and I have found the following:- On OpenJDK 17, calling
Method.setAccessible(true)when it is not necessary, has a performance impact.
I have made a simple Java Application which illustrates how fast is each reflection invocation method.
When calling a method from the same instance, 1000000 times, Java direct call takes 4375692 ns and Java Method invoke API takes 7948120 ns (without callingsetAccessible(true)every time).
When usingsetAccessiblefor every call, the method invocation takes 9676134 ns.
Even that this shows almostMethod.invokeis 3 times slower withsetAccessiblecall, it is actually faster than usingReflectASM, which was being removed from #10071.
The-Dsun.reflect.inflationThreshold=0JVM flag has no effect in this case because the Method.invoke moves to a faster call after 15th run.
The Class.getDeclaredMethod operation is also an expensive operation which needs to be addressed. - On OpenJDK 18+, the standard Reflection invoke call has being replaced internally with
MethodHandlesAPI, which proves that in the simple Java application, it is almost 5 times slower (23355147 ns). I don't know if there is an alternative, but this indicates to us that we need to move to an alternative API (usingMethodHandlesdirectly, or enforce using the old API using JVM args).
#2 Updated by Teodor Gorghe 7 months ago
- Related to Bug #10071: Method code too large - ReflectASM added
#3 Updated by Alexandru Lungu 7 months ago
- Related to Feature #9686: move FWD to Java 25 added
#4 Updated by Alexandru Lungu 7 months ago
- Related to Feature #9685: move FWD to Java 21 added
#5 Updated by Greg Shah 7 months ago
- Related to Feature #6819: refactor FWD proxy implementation to use ReflectASM instead of Java Method reflection added
#7 Updated by Tomasz Domin 7 months ago
Would introducing Java Modularity affect those benchmarks ? There could be some addition access checks.
#8 Updated by Teodor Gorghe 7 months ago
Greg Shah wrote:
Make sure to read #6819, we did some early work there and I completely agree that using
MethodHandleis a really good target.
This is a very good observation.
I have done some tests on a customer application and I have noticed that the execution time for a test suite JDK 17 vs JDK 25 is around the same, with a decrease of 1% on JDK 25. I can't draw a conclusion because most of the time is spent on some operations that is not related to Method.Invoke. The memory usage has been decreased because of the new -XX:+UseCompactObjectHeaders feature. The application startup is also much faster. Note that this testing was done with the old JDK17 bytecode and JVM args which meant to be for JDK 17.
On #6819, I see that there were some attempts on reducing CFO.invoke calls by using lambda methods, which from my perspective, is the fastest. JIT Compiler knows the best how to inline it, making it similar to a direct call.
The benchmarks from #11004-1 were some confirmation tests to confirm what I have read from internet, with a simplified scenario, calling an accessible public method from an instance. I think adding Java Modularity will affect these benchmarks, but both Invocation APIs are affected in the same way in JDK 21+.
Also, starting with JDK 22+, the old Reflection API implementation was completely removed, so, in order to support JDK 25, we can't just use -Djdk.reflect.useDirectMethodHandle=false.