8340456: Reduce overhead of proxying Object methods in ProxyGenerator

Reviewed-by: liach
This commit is contained in:
Claes Redestad 2024-09-20 09:21:12 +00:00
parent 5d611c0377
commit a50440fadc
3 changed files with 55 additions and 75 deletions

View File

@ -114,11 +114,18 @@ final class ProxyGenerator {
private static final Method OBJECT_EQUALS_METHOD; private static final Method OBJECT_EQUALS_METHOD;
private static final Method OBJECT_TO_STRING_METHOD; private static final Method OBJECT_TO_STRING_METHOD;
private static final String OBJECT_HASH_CODE_SIG;
private static final String OBJECT_EQUALS_SIG;
private static final String OBJECT_TO_STRING_SIG;
static { static {
try { try {
OBJECT_HASH_CODE_METHOD = Object.class.getMethod("hashCode"); OBJECT_HASH_CODE_METHOD = Object.class.getMethod("hashCode");
OBJECT_HASH_CODE_SIG = OBJECT_HASH_CODE_METHOD.toShortSignature();
OBJECT_EQUALS_METHOD = Object.class.getMethod("equals", Object.class); OBJECT_EQUALS_METHOD = Object.class.getMethod("equals", Object.class);
OBJECT_EQUALS_SIG = OBJECT_EQUALS_METHOD.toShortSignature();
OBJECT_TO_STRING_METHOD = Object.class.getMethod("toString"); OBJECT_TO_STRING_METHOD = Object.class.getMethod("toString");
OBJECT_TO_STRING_SIG = OBJECT_TO_STRING_METHOD.toShortSignature();
} catch (NoSuchMethodException e) { } catch (NoSuchMethodException e) {
throw new NoSuchMethodError(e.getMessage()); throw new NoSuchMethodError(e.getMessage());
} }
@ -446,9 +453,9 @@ final class ProxyGenerator {
* java.lang.Object take precedence over duplicate methods in the * java.lang.Object take precedence over duplicate methods in the
* proxy interfaces. * proxy interfaces.
*/ */
addProxyMethod(new ProxyMethod(OBJECT_HASH_CODE_METHOD, "m0")); addProxyMethod(new ProxyMethod(OBJECT_HASH_CODE_METHOD, OBJECT_HASH_CODE_SIG, "m0"));
addProxyMethod(new ProxyMethod(OBJECT_EQUALS_METHOD, "m1")); addProxyMethod(new ProxyMethod(OBJECT_EQUALS_METHOD, OBJECT_EQUALS_SIG, "m1"));
addProxyMethod(new ProxyMethod(OBJECT_TO_STRING_METHOD, "m2")); addProxyMethod(new ProxyMethod(OBJECT_TO_STRING_METHOD, OBJECT_TO_STRING_SIG, "m2"));
/* /*
* Accumulate all of the methods from the proxy interfaces. * Accumulate all of the methods from the proxy interfaces.
@ -526,7 +533,7 @@ final class ProxyGenerator {
return; return;
} }
} }
sigmethods.add(new ProxyMethod(m, sig, m.getSharedParameterTypes(), returnType, sigmethods.add(new ProxyMethod(m, sig, returnType,
exceptionTypes, fromClass, "m" + proxyMethodCount++)); exceptionTypes, fromClass, "m" + proxyMethodCount++));
} }
@ -617,11 +624,11 @@ final class ProxyGenerator {
Label failLabel = cob.newLabel(); Label failLabel = cob.newLabel();
ClassEntry mhl = cp.classEntry(CD_MethodHandles_Lookup); ClassEntry mhl = cp.classEntry(CD_MethodHandles_Lookup);
ClassEntry iae = cp.classEntry(CD_IllegalAccessException); ClassEntry iae = cp.classEntry(CD_IllegalAccessException);
cob.aload(cob.parameterSlot(0)) cob.aload(0)
.invokevirtual(cp.methodRefEntry(mhl, cp.nameAndTypeEntry("lookupClass", MTD_Class))) .invokevirtual(cp.methodRefEntry(mhl, cp.nameAndTypeEntry("lookupClass", MTD_Class)))
.ldc(proxyCE) .ldc(proxyCE)
.if_acmpne(failLabel) .if_acmpne(failLabel)
.aload(cob.parameterSlot(0)) .aload(0)
.invokevirtual(cp.methodRefEntry(mhl, cp.nameAndTypeEntry("hasFullPrivilegeAccess", MTD_boolean))) .invokevirtual(cp.methodRefEntry(mhl, cp.nameAndTypeEntry("hasFullPrivilegeAccess", MTD_boolean)))
.ifeq(failLabel) .ifeq(failLabel)
.invokestatic(CD_MethodHandles, "lookup", MTD_MethodHandles$Lookup) .invokestatic(CD_MethodHandles, "lookup", MTD_MethodHandles$Lookup)
@ -629,7 +636,7 @@ final class ProxyGenerator {
.labelBinding(failLabel) .labelBinding(failLabel)
.new_(iae) .new_(iae)
.dup() .dup()
.aload(cob.parameterSlot(0)) .aload(0)
.invokevirtual(cp.methodRefEntry(mhl, cp.nameAndTypeEntry("toString", MTD_String))) .invokevirtual(cp.methodRefEntry(mhl, cp.nameAndTypeEntry("toString", MTD_String)))
.invokespecial(cp.methodRefEntry(iae, exInit)) .invokespecial(cp.methodRefEntry(iae, exInit))
.athrow() .athrow()
@ -650,18 +657,16 @@ final class ProxyGenerator {
private final Method method; private final Method method;
private final String shortSignature; private final String shortSignature;
private final Class<?> fromClass; private final Class<?> fromClass;
private final Class<?>[] parameterTypes;
private final Class<?> returnType; private final Class<?> returnType;
private final String methodFieldName; private final String methodFieldName;
private Class<?>[] exceptionTypes; private Class<?>[] exceptionTypes;
private final FieldRefEntry methodField; private final FieldRefEntry methodField;
private ProxyMethod(Method method, String sig, Class<?>[] parameterTypes, private ProxyMethod(Method method, String sig,
Class<?> returnType, Class<?>[] exceptionTypes, Class<?> returnType, Class<?>[] exceptionTypes,
Class<?> fromClass, String methodFieldName) { Class<?> fromClass, String methodFieldName) {
this.method = method; this.method = method;
this.shortSignature = sig; this.shortSignature = sig;
this.parameterTypes = parameterTypes;
this.returnType = returnType; this.returnType = returnType;
this.exceptionTypes = exceptionTypes; this.exceptionTypes = exceptionTypes;
this.fromClass = fromClass; this.fromClass = fromClass;
@ -670,14 +675,17 @@ final class ProxyGenerator {
cp.nameAndTypeEntry(methodFieldName, CD_Method)); cp.nameAndTypeEntry(methodFieldName, CD_Method));
} }
private Class<?>[] parameterTypes() {
return method.getSharedParameterTypes();
}
/** /**
* Create a new specific ProxyMethod with a specific field name * Create a new specific ProxyMethod with a specific field name
* *
* @param method The method for which to create a proxy * @param method The method for which to create a proxy
*/ */
private ProxyMethod(Method method, String methodFieldName) { private ProxyMethod(Method method, String sig, String methodFieldName) {
this(method, method.toShortSignature(), this(method, sig, method.getReturnType(),
method.getSharedParameterTypes(), method.getReturnType(),
method.getSharedExceptionTypes(), method.getDeclaringClass(), methodFieldName); method.getSharedExceptionTypes(), method.getDeclaringClass(), methodFieldName);
} }
@ -685,17 +693,18 @@ final class ProxyGenerator {
* Generate this method, including the code and exception table entry. * Generate this method, including the code and exception table entry.
*/ */
private void generateMethod(ClassBuilder clb) { private void generateMethod(ClassBuilder clb) {
var desc = methodTypeDesc(returnType, parameterTypes); var desc = methodTypeDesc(returnType, parameterTypes());
int accessFlags = (method.isVarArgs()) ? ACC_VARARGS | ACC_PUBLIC | ACC_FINAL int accessFlags = (method.isVarArgs()) ? ACC_VARARGS | ACC_PUBLIC | ACC_FINAL
: ACC_PUBLIC | ACC_FINAL; : ACC_PUBLIC | ACC_FINAL;
var catchList = computeUniqueCatchList(exceptionTypes);
clb.withMethod(method.getName(), desc, accessFlags, mb -> clb.withMethod(method.getName(), desc, accessFlags, mb ->
mb.with(ExceptionsAttribute.of(toClassEntries(cp, List.of(exceptionTypes)))) mb.with(ExceptionsAttribute.of(toClassEntries(cp, List.of(exceptionTypes))))
.withCode(cob -> { .withCode(cob -> {
var catchList = computeUniqueCatchList(exceptionTypes);
cob.aload(cob.receiverSlot()) cob.aload(cob.receiverSlot())
.getfield(handlerField) .getfield(handlerField)
.aload(cob.receiverSlot()) .aload(cob.receiverSlot())
.getstatic(methodField); .getstatic(methodField);
Class<?>[] parameterTypes = parameterTypes();
if (parameterTypes.length > 0) { if (parameterTypes.length > 0) {
// Create an array and fill with the parameters converting primitives to wrappers // Create an array and fill with the parameters converting primitives to wrappers
cob.loadConstant(parameterTypes.length) cob.loadConstant(parameterTypes.length)
@ -784,6 +793,7 @@ final class ProxyGenerator {
var cp = cob.constantPool(); var cp = cob.constantPool();
codeClassForName(cob, fromClass); codeClassForName(cob, fromClass);
Class<?>[] parameterTypes = parameterTypes();
cob.ldc(method.getName()) cob.ldc(method.getName())
.loadConstant(parameterTypes.length) .loadConstant(parameterTypes.length)
.anewarray(classCE); .anewarray(classCE);
@ -817,10 +827,14 @@ final class ProxyGenerator {
* loader is anticipated at local variable index 0. * loader is anticipated at local variable index 0.
*/ */
private void codeClassForName(CodeBuilder cob, Class<?> cl) { private void codeClassForName(CodeBuilder cob, Class<?> cl) {
cob.ldc(cl.getName()) if (cl == Object.class) {
.iconst_0() // false cob.ldc(objectCE);
.aload(0)// classLoader } else {
.invokestatic(classForName); cob.ldc(cl.getName())
.iconst_0() // false
.aload(0)// classLoader
.invokestatic(classForName);
}
} }
@Override @Override

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -26,7 +26,6 @@ import java.util.List;
import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.CompilerControl;
import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.Mode;
@ -36,37 +35,29 @@ import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup; import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.Blackhole;
import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /**
* Benchmark measuring java.lang.reflect.ProxyGenerator.generateProxyClass. * Benchmark measuring java.lang.reflect.ProxyGenerator.generateProxyClass.
* It bypasses the cache of proxies to measure the time to construct a proxy. * It bypasses the cache of proxies to measure the time to construct a proxy.
*/ */
@Warmup(iterations = 5) @Warmup(iterations = 5, time = 2)
@Measurement(iterations = 10) @Measurement(iterations = 5, time = 2)
@Fork(value = 1) @Fork(value = 1, jvmArgsPrepend = "--add-opens=java.base/java.lang.reflect=ALL-UNNAMED")
@BenchmarkMode(Mode.AverageTime) @BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS) @OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread) @State(Scope.Thread)
public class ProxyPerf { public class ProxyGeneratorBench {
/** /**
* Sample results from a Dell T7610. * Sample results from a Dell T7610.
* Benchmark Mode Cnt Score Error Units * Benchmark Mode Cnt Score Error Units
* ProxyPerf.genIntf_1 avgt 10 35325.428 +/- 780.459 ns/op * ProxyPerf.genIntf_1 avgt 10 35325.428 +/- 780.459 ns/op
* ProxyPerf.genIntf_1_V49 avgt 10 34309.423 +/- 727.188 ns/op
* ProxyPerf.genStringsIntf_3 avgt 10 46600.366 +/- 663.812 ns/op * ProxyPerf.genStringsIntf_3 avgt 10 46600.366 +/- 663.812 ns/op
* ProxyPerf.genStringsIntf_3_V49 avgt 10 45911.817 +/- 1598.536 ns/op
* ProxyPerf.genZeroParams avgt 10 33245.048 +/- 437.988 ns/op * ProxyPerf.genZeroParams avgt 10 33245.048 +/- 437.988 ns/op
* ProxyPerf.genZeroParams_V49 avgt 10 32954.254 +/- 1041.932 ns/op * ProxyPerf.genPrimsIntf_2 avgt 10 43987.819 +/- 837.443 ns/op
* ProxyPerf.getPrimsIntf_2 avgt 10 43987.819 +/- 837.443 ns/op
* ProxyPerf.getPrimsIntf_2_V49 avgt 10 42863.462 +/- 1193.480 ns/op
*/ */
public interface Intf_1 { public interface Intf_1 {
@ -84,7 +75,6 @@ public class ProxyPerf {
public String m2String(String s1, String s2); public String m2String(String s1, String s2);
} }
private InvocationHandler handler;
private ClassLoader classloader; private ClassLoader classloader;
private Method proxyGen; private Method proxyGen;
private Method proxyGenV49; private Method proxyGenV49;
@ -92,19 +82,11 @@ public class ProxyPerf {
@Setup @Setup
public void setup() { public void setup() {
try { try {
handler = (Object proxy, Method method, Object[] args) -> null;
classloader = ClassLoader.getSystemClassLoader(); classloader = ClassLoader.getSystemClassLoader();
Class<?> proxyGenClass = Class.forName("java.lang.reflect.ProxyGenerator"); Class<?> proxyGenClass = Class.forName("java.lang.reflect.ProxyGenerator");
proxyGen = proxyGenClass.getDeclaredMethod("generateProxyClass", proxyGen = proxyGenClass.getDeclaredMethod("generateProxyClass",
ClassLoader.class, String.class, java.util.List.class, int.class); ClassLoader.class, String.class, java.util.List.class, int.class);
proxyGen.setAccessible(true); proxyGen.setAccessible(true);
// Init access to the old Proxy generator
Class<?> proxyGenClassV49 = Class.forName("java.lang.reflect.ProxyGenerator_v49");
proxyGenV49 = proxyGenClassV49.getDeclaredMethod("generateProxyClass",
String.class, java.util.List.class, int.class);
proxyGenV49.setAccessible(true);
} catch (Exception ex) { } catch (Exception ex) {
ex.printStackTrace(); ex.printStackTrace();
throw new RuntimeException("ProxyClass setup fails", ex); throw new RuntimeException("ProxyClass setup fails", ex);
@ -112,51 +94,35 @@ public class ProxyPerf {
} }
@Benchmark @Benchmark
public void genZeroParams(Blackhole bh) throws Exception { public Object genZeroParams() throws Exception {
List<Class<?>> interfaces = List.of(Runnable.class); List<Class<?>> interfaces = List.of(Runnable.class);
bh.consume(proxyGen.invoke(null, classloader, "ProxyImpl", interfaces, 1)); return proxyGen.invoke(null, classloader, "ProxyImpl", interfaces, 1);
} }
@Benchmark @Benchmark
public void genIntf_1(Blackhole bh) throws Exception { public Object genIntf_1() throws Exception {
List<Class<?>> interfaces = List.of(Intf_1.class); List<Class<?>> interfaces = List.of(Intf_1.class);
bh.consume(proxyGen.invoke(null, classloader, "ProxyImpl", interfaces, 1)); return proxyGen.invoke(null, classloader, "ProxyImpl", interfaces, 1);
} }
@Benchmark @Benchmark
public void getPrimsIntf_2(Blackhole bh) throws Exception { public Object genPrimsIntf_2() throws Exception {
List<Class<?>> interfaces = List.of(Intf_2.class); List<Class<?>> interfaces = List.of(Intf_2.class);
bh.consume(proxyGen.invoke(null, classloader, "ProxyImpl", interfaces, 1)); return proxyGen.invoke(null, classloader, "ProxyImpl", interfaces, 1);
} }
@Benchmark @Benchmark
public void genStringsIntf_3(Blackhole bh) throws Exception { public Object genStringsIntf_3() throws Exception {
List<Class<?>> interfaces = List.of(Intf_3.class); List<Class<?>> interfaces = List.of(Intf_3.class);
bh.consume(proxyGen.invoke(null, classloader, "ProxyImpl", interfaces, 1)); return proxyGen.invoke(null, classloader, "ProxyImpl", interfaces, 1);
} }
// Generate using the V49inal generator for comparison public static void main(String... args) throws Exception {
var benchmark = new ProxyGeneratorBench();
@Benchmark benchmark.setup();
public void genZeroParams_V49(Blackhole bh) throws Exception { benchmark.genZeroParams();
List<Class<?>> interfaces = List.of(Runnable.class); benchmark.genIntf_1();
bh.consume(proxyGenV49.invoke(null, "ProxyImpl", interfaces, 1)); benchmark.genPrimsIntf_2();
benchmark.genStringsIntf_3();
} }
@Benchmark
public void genIntf_1_V49(Blackhole bh) throws Exception {
List<Class<?>> interfaces = List.of(Intf_1.class);
bh.consume(proxyGenV49.invoke(null, "ProxyImpl", interfaces, 1));
}
@Benchmark
public void getPrimsIntf_2_V49(Blackhole bh) throws Exception {
List<Class<?>> interfaces = List.of(Intf_2.class);
bh.consume(proxyGenV49.invoke(null, "ProxyImpl", interfaces, 1));
}
@Benchmark
public void genStringsIntf_3_V49(Blackhole bh) throws Exception {
List<Class<?>> interfaces = List.of(Intf_3.class);
bh.consume(proxyGenV49.invoke(null, "ProxyImpl", interfaces, 1));
}
} }