Skip to content

Instantly share code, notes, and snippets.

@blackdrag
Last active September 5, 2023 06:34
Show Gist options
  • Save blackdrag/28df334a8f49f06048d19848a50828c8 to your computer and use it in GitHub Desktop.
Save blackdrag/28df334a8f49f06048d19848a50828c8 to your computer and use it in GitHub Desktop.
simple indy test with JDK 17
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