Last active
September 5, 2023 06:34
-
-
Save blackdrag/28df334a8f49f06048d19848a50828c8 to your computer and use it in GitHub Desktop.
simple indy test with JDK 17
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package indy; | |
import org.junit.Test; | |
import org.objectweb.asm.*; | |
import java.io.FileOutputStream; | |
import java.io.IOException; | |
import java.lang.invoke.*; | |
import java.lang.reflect.InvocationTargetException; | |
import java.lang.reflect.Method; | |
import java.util.function.Consumer; | |
import static org.objectweb.asm.Opcodes.ACC_PUBLIC; | |
public class IndyCallsiteTests { | |
private static final int WARM_UP_ITERATIONS = 10; | |
private static final int TEST_ITERATIONS = 10; | |
private static final int HOT_LOOP_ITERATIONS = 50_000; | |
private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); | |
private static final String BSM_DESCRIPTOR = MethodType.methodType( | |
CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class | |
).toMethodDescriptorString(); | |
private static final Handle BSM = new Handle(Opcodes.H_INVOKESTATIC, | |
IndyCallsiteTests.class.getName().replace('.', '/'), "bootstrap", BSM_DESCRIPTOR, false | |
); | |
private static MethodHandle selector, selector2; | |
static { | |
try { | |
selector = LOOKUP.findStatic(IndyCallsiteTests.class, "selector", MethodType.methodType(int.class, MethodHandles.Lookup.class, MutableCallSite.class, String.class, int.class)); | |
selector2 = LOOKUP.findStatic(IndyCallsiteTests.class, "selector2", MethodType.methodType(int.class, int.class)); | |
} catch (NoSuchMethodException | IllegalAccessException e) { | |
throw new RuntimeException(e); | |
} | |
} | |
@SuppressWarnings("unused") | |
public static CallSite bootstrap(MethodHandles.Lookup caller, String name, MethodType type) { | |
MethodHandle handle; | |
try { | |
handle = caller.findStatic(IndyCallsiteTests.class, name, type); | |
} catch (NoSuchMethodException | IllegalAccessException e) { | |
throw new RuntimeException(e); | |
} | |
return new ConstantCallSite(handle); | |
/*MutableCallSite callsite = new MutableCallSite(type); | |
handle = MethodHandles.insertArguments(selector, 0, caller, callsite, name); | |
callsite.setTarget(handle); | |
return callsite;*/ | |
//return new ConstantCallSite(selector2); | |
} | |
public static int selector2(int i) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { | |
Method m = IndyCallsiteTests.class.getMethod("foo", int.class); | |
return (int) m.invoke(null, i); | |
} | |
public static int selector(MethodHandles.Lookup caller, MutableCallSite callsite, String name, int i) { | |
MethodHandle handle; | |
try { | |
handle = caller.findStatic(IndyCallsiteTests.class, name, callsite.type()); | |
} catch (NoSuchMethodException | IllegalAccessException e) { | |
throw new RuntimeException(e); | |
} | |
callsite.setTarget(handle); | |
try { | |
return (int) handle.invokeExact(i); | |
} catch (Throwable e) { | |
throw new RuntimeException(e); | |
} | |
} | |
public static int foo(int i) { | |
return i; | |
} | |
@Test | |
public void nonDyamicCall() throws ReflectiveOperationException, IOException { | |
System.out.println("indyCall"); | |
Class<? extends Runnable> c = writeIndyCall(BSM, "foo", "(I)I", (mv -> { | |
mv.visitInsn(Opcodes.ICONST_1); | |
})); | |
Runnable r = c.getDeclaredConstructor().newInstance(); | |
perf(r); | |
} | |
@Test | |
public void indy2() throws ReflectiveOperationException, IOException { | |
System.out.println("indyCall2"); | |
Class<? extends Runnable> c = writeIndyCall(BSM, "foo", "(I)I", (mv -> { | |
mv.visitInsn(Opcodes.ICONST_1); | |
})); | |
Runnable r = c.getDeclaredConstructor().newInstance(); | |
perf(r); | |
} | |
@Test | |
public void reflectiveCall() { | |
System.out.println("reflectiveCall"); | |
Runnable r = new Runnable() { | |
@Override | |
public void run() { | |
try { | |
Method m = IndyCallsiteTests.class.getMethod("foo", int.class); | |
m.invoke(null, 1); | |
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { | |
throw new RuntimeException(e); | |
} | |
} | |
}; | |
perf(r); | |
} | |
@Test | |
public void reflectiveCallCached() { | |
System.out.println("reflectiveCallCached"); | |
Runnable r = new Runnable() { | |
Method m = null; | |
@Override | |
public void run() { | |
try { | |
if (m == null) { | |
m = IndyCallsiteTests.class.getMethod("foo", int.class); | |
} | |
m.invoke(null, 1); | |
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { | |
throw new RuntimeException(e); | |
} | |
} | |
}; | |
perf(r); | |
} | |
public static void perf(Runnable r) { | |
System.out.println("------------------------------------"); | |
long time0 = System.nanoTime(); | |
r.run(); | |
long time1 = System.nanoTime(); | |
print("init time", time0, time1, 1); | |
for (int i = 0; i<= WARM_UP_ITERATIONS; i++) { | |
long w1 = System.nanoTime(); | |
r.run(); | |
long w2 = System.nanoTime(); | |
print("warmup time per iteration", w1, w2, 1); | |
} | |
System.out.println("------------------------------------"); | |
for (int j = 0; j<= TEST_ITERATIONS; j++) { | |
long timeStart = System.nanoTime(); | |
for (int i = 0; i <= HOT_LOOP_ITERATIONS; i++) { | |
r.run(); | |
} | |
long timeEnd = System.nanoTime(); | |
print("avg time in iteration " + j, timeStart, timeEnd, HOT_LOOP_ITERATIONS); | |
} | |
System.out.println("------------------------------------"); | |
} | |
private static int runnerCounter = 0; | |
private Class<? extends Runnable> writeIndyCall( | |
Handle bootstrap, String message, String messageSignature, Consumer<MethodVisitor> argWriter | |
) throws IllegalAccessException, IOException { | |
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); | |
cw.visit( | |
Opcodes.V17, Opcodes.ACC_SUPER | Opcodes.ACC_PUBLIC, | |
"indy/Runner" + runnerCounter++, | |
null, "java/lang/Object", new String[]{Runnable.class.getName().replace('.', '/')} | |
); | |
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "run", "()V", null, new String[0]); | |
argWriter.accept(mv); | |
mv.visitInvokeDynamicInsn(message, messageSignature, bootstrap); | |
mv.visitInsn(Opcodes.POP); | |
mv.visitInsn(Opcodes.RETURN); | |
mv.visitMaxs(0, 0); | |
mv.visitEnd(); | |
mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null); | |
mv.visitIntInsn(Opcodes.ALOAD, 0); | |
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false); | |
mv.visitInsn(Opcodes.RETURN); | |
mv.visitMaxs(0, 0); | |
mv.visitEnd(); | |
cw.visitSource("Runner.dyn", null); | |
cw.visitEnd(); | |
byte[] bytes = cw.toByteArray(); | |
try(FileOutputStream fos = new FileOutputStream("Runner.class")){ | |
fos.write(bytes); | |
fos.flush(); | |
} | |
return defineClass(bytes); | |
} | |
@SuppressWarnings("unchecked") | |
private Class<? extends Runnable> defineClass(byte[] code) throws IllegalAccessException { | |
return (Class<? extends Runnable>) LOOKUP.defineClass(code); | |
} | |
private static void print(String text, long t1, long t2, int runs) { | |
System.out.println(text + " with runs " + runs +": (ns) " + (t2-t1)/runs); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment