RFR: 8159746: (proxy) Support for default methods
Peter Levart
plevart at openjdk.java.net
Fri Nov 20 11:21:03 UTC 2020
On Fri, 20 Nov 2020 10:55:21 GMT, Peter Levart <plevart at openjdk.org> wrote:
>> The trick is that if we know that a class like java.lang.invoke.Proxy may exist,
>> it means that instead of distorting the j.l.r.Proxy API to increase of few percents the performance when calling a default method, you can come with a simpler design in term of API that just add an API point to call a default method.
>>
>> Better performance becoming on the the goals of java.lang.invoke.Proxy.
>
> Hi Mandy,
> I re-ran the benchmark on your latest version (the static API) and I get similar results as you:
>
> Benchmark Mode Cnt Score Error Units
> ProxyBench.implClass avgt 5 3.745 ± 0.033 ns/op
> ProxyBench.implProxy avgt 5 29.826 ± 0.183 ns/op
> ProxyBench.ppImplClass avgt 5 3.683 ± 0.009 ns/op
> ProxyBench.ppImplProxy avgt 5 29.124 ± 0.535 ns/op
>
> I also tried a variant where the access check in the invokeDefault static method is not pre-screened with checking of the interface modifiers and package export status but relies on Method.checkAccess cache alone:
>
> // access check
> Class<?> caller = Reflection.getCallerClass();
> int modifiers = method.getModifiers();
> method.checkAccess(caller, intf, proxyClass, modifiers);
>
> ... and the results are not worse, even marginally better:
>
> Benchmark Mode Cnt Score Error Units
> ProxyBench.implClass avgt 5 3.724 ± 0.012 ns/op
> ProxyBench.implProxy avgt 5 29.138 ± 0.271 ns/op
> ProxyBench.ppImplClass avgt 5 3.744 ± 0.009 ns/op
> ProxyBench.ppImplProxy avgt 5 28.961 ± 0.182 ns/op
>
> I think this looks reasonably good.
In my previous attempts when I was modifying the ProxyGenerator I noticed that generated proxy fields holding Method objects are just "static" but could be "static final". If they were "static final", JIT could constant-fold the Method instances. If this was combined with marking some fields in Method as @Stable, this could improve optimization of code a bit further. I did this with the following patch:
Index: src/java.base/share/classes/java/lang/reflect/InvocationHandler.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- src/java.base/share/classes/java/lang/reflect/InvocationHandler.java (revision 8352a20bf54a085a97d3f703b5dab482d3f9eccc)
+++ src/java.base/share/classes/java/lang/reflect/InvocationHandler.java (date 1605869089804)
@@ -271,15 +271,10 @@
throw new IllegalArgumentException(""" + method + "" is not a default method");
}
Class<?> intf = method.getDeclaringClass();
- // access check if it is a non-public proxy interface or not unconditionally exported
- if (!Modifier.isPublic(intf.getModifiers()) ||
- !intf.getModule().isExported(intf.getPackageName())) {
- // throw IAE if the caller class has no access to the default method
- // same access check to Method::invoke on the default method
- int modifiers = method.getModifiers();
- Class<?> caller = Reflection.getCallerClass();
- method.checkAccess(caller, intf, proxyClass, modifiers);
- }
+ // access check
+ Class<?> caller = Reflection.getCallerClass();
+ int modifiers = method.getModifiers();
+ method.checkAccess(caller, intf, proxyClass, modifiers);
MethodHandle mh = Proxy.defaultMethodHandle(proxyClass, method);
// invoke the super method
Index: src/java.base/share/classes/java/lang/reflect/Method.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- src/java.base/share/classes/java/lang/reflect/Method.java (revision 8352a20bf54a085a97d3f703b5dab482d3f9eccc)
+++ src/java.base/share/classes/java/lang/reflect/Method.java (date 1605870445135)
@@ -31,6 +31,7 @@
import jdk.internal.reflect.Reflection;
import jdk.internal.vm.annotation.ForceInline;
import jdk.internal.vm.annotation.IntrinsicCandidate;
+import jdk.internal.vm.annotation.Stable;
import sun.reflect.annotation.ExceptionProxy;
import sun.reflect.annotation.TypeNotPresentExceptionProxy;
import sun.reflect.generics.repository.MethodRepository;
@@ -66,7 +67,7 @@
* @since 1.1
*/
public final class Method extends Executable {
- private Class<?> clazz;
+ @Stable private Class<?> clazz;
private int slot;
// This is guaranteed to be interned by the VM in the 1.4
// reflection implementation
@@ -74,7 +75,7 @@
private Class<?> returnType;
private Class<?>[] parameterTypes;
private Class<?>[] exceptionTypes;
- private int modifiers;
+ @Stable private int modifiers;
// Generics and annotations support
private transient String signature;
// generic info repository; lazily initialized
Index: src/java.base/share/classes/java/lang/reflect/ProxyGenerator.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- src/java.base/share/classes/java/lang/reflect/ProxyGenerator.java (revision 8352a20bf54a085a97d3f703b5dab482d3f9eccc)
+++ src/java.base/share/classes/java/lang/reflect/ProxyGenerator.java (date 1605870027579)
@@ -490,7 +490,7 @@
for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
for (ProxyMethod pm : sigmethods) {
// add static field for the Method object
- visitField(Modifier.PRIVATE | Modifier.STATIC, pm.methodFieldName,
+ visitField(Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL, pm.methodFieldName,
LJLR_METHOD, null, null);
// Generate code for proxy method
...and this makes the following results:
Benchmark Mode Cnt Score Error Units
ProxyBench.implClass avgt 5 3.766 ± 0.040 ns/op
ProxyBench.implProxy avgt 5 26.847 ± 0.626 ns/op
ProxyBench.ppImplClass avgt 5 3.700 ± 0.017 ns/op
ProxyBench.ppImplProxy avgt 5 26.322 ± 0.048 ns/op
But this can be changed as a follow-up patch that also takes care of Constructor and Field.
-------------
PR: https://git.openjdk.java.net/jdk/pull/313
More information about the core-libs-dev
mailing list