Skip to content

Instantly share code, notes, and snippets.

@raphw
Last active March 4, 2024 00:14
  • Star 26 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save raphw/881e1745996f9d314ab0 to your computer and use it in GitHub Desktop.
Java MethodHandle and reflection benchmark
package benchmark;
import org.openjdk.jmh.annotations.*;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.TimeUnit;
@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class FieldBenchmark {
private String value = "foo";
private int primitiveValue = 42;
enum Access {
INSTANCE;
private String value = "bar";
}
private Field reflective, reflectiveAccessible, reflectivePrimitive, reflectiveAccessiblePrimitive, reflectiveAccessiblePrivate;
private MethodHandle methodHandle, methodHandleUnreflected, methodHandlePrimitive, methodHandleUnreflectedPrimitive, methodHandleUnreflectedPrivate;
private static final MethodHandle METHOD_HANDLE_INLINE, METHOD_HANDLE_UNREFLECTED_INLINE, METHOD_HANDLE_PRIMITIVE_INLINE, METHOD_HANDLE_UNREFLECTED_PRIMITIVE, METHOD_HANDLE_UNREFLECTED_PRIVATE;
static {
try {
METHOD_HANDLE_INLINE = MethodHandles.lookup().findGetter(FieldBenchmark.class, "value", String.class);
METHOD_HANDLE_UNREFLECTED_INLINE = MethodHandles.lookup().unreflectGetter(FieldBenchmark.class.getDeclaredField("value"));
METHOD_HANDLE_PRIMITIVE_INLINE = MethodHandles.lookup().findGetter(FieldBenchmark.class, "primitiveValue", int.class);
METHOD_HANDLE_UNREFLECTED_PRIMITIVE = MethodHandles.lookup().unreflectGetter(FieldBenchmark.class.getDeclaredField("primitiveValue"));
Field reflectiveAccessiblePrivate = Access.class.getDeclaredField("value");
reflectiveAccessiblePrivate.setAccessible(true);
METHOD_HANDLE_UNREFLECTED_PRIVATE = MethodHandles.lookup().unreflectGetter(reflectiveAccessiblePrivate);
} catch (Exception e) {
throw new AssertionError();
}
}
@Setup
public void setup() throws Exception {
reflective = FieldBenchmark.class.getDeclaredField("value");
reflectiveAccessible = FieldBenchmark.class.getDeclaredField("value");
reflectiveAccessible.setAccessible(true);
reflectivePrimitive = FieldBenchmark.class.getDeclaredField("primitiveValue");
reflectiveAccessiblePrimitive = FieldBenchmark.class.getDeclaredField("primitiveValue");
reflectiveAccessiblePrimitive.setAccessible(true);
methodHandle = MethodHandles.lookup().findGetter(FieldBenchmark.class, "value", String.class);
methodHandleUnreflected = MethodHandles.lookup().unreflectGetter(reflective);
methodHandlePrimitive = MethodHandles.lookup().findGetter(FieldBenchmark.class, "primitiveValue", int.class);
methodHandleUnreflectedPrimitive = MethodHandles.lookup().unreflectGetter(reflectivePrimitive);
reflectiveAccessiblePrivate = Access.class.getDeclaredField("value");
reflectiveAccessiblePrivate.setAccessible(true);
methodHandleUnreflectedPrivate = MethodHandles.lookup().unreflectGetter(reflectiveAccessiblePrivate);
}
@Benchmark
public Object normal() {
return value;
}
@Benchmark
public Object reflection() throws InvocationTargetException, IllegalAccessException {
return reflective.get(this);
}
@Benchmark
public Object reflectionAccessible() throws InvocationTargetException, IllegalAccessException {
return reflectiveAccessible.get(this);
}
@Benchmark
public Object handle() throws Throwable {
return methodHandle.invoke(this);
}
@Benchmark
public Object handleExact() throws Throwable {
return (String) methodHandle.invokeExact(this);
}
@Benchmark
public Object handleUnreflected() throws Throwable {
return methodHandleUnreflected.invoke(this);
}
@Benchmark
public Object handleUnreflectedExact() throws Throwable {
return (String) methodHandleUnreflected.invokeExact(this);
}
@Benchmark
public int primitive() {
return primitiveValue;
}
@Benchmark
public int reflectionPrimitive() throws InvocationTargetException, IllegalAccessException {
return (int) reflectivePrimitive.get(this);
}
@Benchmark
public int reflectionAccessiblePrimitive() throws InvocationTargetException, IllegalAccessException {
return (int) reflectiveAccessiblePrimitive.get(this);
}
@Benchmark
public int reflectionSpecializedPrimitive() throws InvocationTargetException, IllegalAccessException {
return reflectivePrimitive.getInt(this);
}
@Benchmark
public int reflectionAccessibleSpecializedPrimitive() throws InvocationTargetException, IllegalAccessException {
return reflectiveAccessiblePrimitive.getInt(this);
}
@Benchmark
public int handlePrimitive() throws Throwable {
return (int) methodHandlePrimitive.invoke(this);
}
@Benchmark
public int handleExactPrimitive() throws Throwable {
return (int) methodHandlePrimitive.invokeExact(this);
}
@Benchmark
public int handleUnreflectedPrimitive() throws Throwable {
return (int) methodHandleUnreflectedPrimitive.invoke(this);
}
@Benchmark
public int handleUnreflectedExactPrimitive() throws Throwable {
return (int) methodHandleUnreflectedPrimitive.invokeExact(this);
}
@Benchmark
public String privateNormal() {
return Access.INSTANCE.value; // accessor method
}
@Benchmark
public Object reflectionAccessiblePrivate() throws Exception {
return reflectiveAccessiblePrivate.get(Access.INSTANCE);
}
@Benchmark
public String handleUnreflectedPrivate() throws Throwable {
return (String) methodHandleUnreflectedPrivate.invokeExact((Access) Access.INSTANCE);
}
@Benchmark
public Object handleInline() throws Throwable {
return METHOD_HANDLE_INLINE.invoke(this);
}
@Benchmark
public Object handleExactInline() throws Throwable {
return (String) METHOD_HANDLE_INLINE.invokeExact(this);
}
@Benchmark
public Object handleUnreflectedInline() throws Throwable {
return METHOD_HANDLE_UNREFLECTED_INLINE.invoke(this);
}
@Benchmark
public Object handleUnreflectedExactInline() throws Throwable {
return (String) METHOD_HANDLE_UNREFLECTED_INLINE.invokeExact(this);
}
@Benchmark
public int handlePrimitiveInline() throws Throwable {
return (int) METHOD_HANDLE_PRIMITIVE_INLINE.invoke(this);
}
@Benchmark
public int handleExactPrimitiveInline() throws Throwable {
return (int) METHOD_HANDLE_PRIMITIVE_INLINE.invokeExact(this);
}
@Benchmark
public int handleUnreflectedPrimitiveInline() throws Throwable {
return (int) METHOD_HANDLE_UNREFLECTED_PRIMITIVE.invoke(this);
}
@Benchmark
public int handleUnreflectedExactPrimitiveInline() throws Throwable {
return (int) METHOD_HANDLE_UNREFLECTED_PRIMITIVE.invokeExact(this);
}
@Benchmark
public String handleUnreflectedPrivateInline() throws Throwable {
return (String) METHOD_HANDLE_UNREFLECTED_PRIVATE.invokeExact((Access) Access.INSTANCE);
}
}
package benchmark;
import org.openjdk.jmh.annotations.*;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class InvocationBenchmark {
private String s1 = "foo", s2 = "bar", s3 = "qux", s4 = "baz";
private String method(String a, String b, String c, String d) {
return a + b + c + d;
}
private int i1 = 1, i2 = 2, i3 = 3, i4 = 4;
private int methodPrimitive(int a, int b, int c, int d) {
return a + b + c + d;
}
enum Access {
INSTANCE;
private String method(String a, String b, String c, String d) {
return a + b + c + d;
}
}
private Method method, methodAccessible, methodPrimitive, methodAccessiblePrimitive, methodAccessiblePrivate;
private MethodHandle methodHandle, methodHandleUnreflected, methodHandlePrimitive, methodHandleUnreflectedPrimitive, methodHandleUnreflectedPrivate;
private static final MethodHandle METHOD_HANDLE_INLINE, METHOD_HANDLE_UNREFLECTED_INLINE, METHOD_HANDLE_PRIMITIVE_INLINE, METHOD_HANDLE_UNREFLECTED_PRIMITIVE_INLINE, METHOD_HANDLE_UNREFLECTED_PRIVATE_INLINE;
static {
try {
Method methodAccessible = InvocationBenchmark.class.getDeclaredMethod("method", String.class, String.class, String.class, String.class);
methodAccessible.setAccessible(true);
METHOD_HANDLE_INLINE = MethodHandles.lookup().findVirtual(InvocationBenchmark.class, "method",
MethodType.methodType(String.class, String.class, String.class, String.class, String.class));
METHOD_HANDLE_UNREFLECTED_INLINE = MethodHandles.lookup().unreflect(methodAccessible);
Method methodAccessiblePrimitive = InvocationBenchmark.class.getDeclaredMethod("methodPrimitive", int.class, int.class, int.class, int.class);
methodAccessiblePrimitive.setAccessible(true);
METHOD_HANDLE_PRIMITIVE_INLINE = MethodHandles.lookup().findVirtual(InvocationBenchmark.class, "methodPrimitive",
MethodType.methodType(int.class, int.class, int.class, int.class, int.class));
METHOD_HANDLE_UNREFLECTED_PRIMITIVE_INLINE = MethodHandles.lookup().unreflect(methodAccessiblePrimitive);
Method methodAccessiblePrivate = Access.class.getDeclaredMethod("method", String.class, String.class, String.class, String.class);
methodAccessiblePrivate.setAccessible(true);
METHOD_HANDLE_UNREFLECTED_PRIVATE_INLINE = MethodHandles.lookup().unreflect(methodAccessiblePrivate);
} catch (Exception e) {
throw new AssertionError();
}
}
@Setup
public void setUp() throws Exception {
method = InvocationBenchmark.class.getDeclaredMethod("method", String.class, String.class, String.class, String.class);
methodAccessible = InvocationBenchmark.class.getDeclaredMethod("method", String.class, String.class, String.class, String.class);
methodAccessible.setAccessible(true);
methodHandle = MethodHandles.lookup().findVirtual(InvocationBenchmark.class, "method",
MethodType.methodType(String.class, String.class, String.class, String.class, String.class));
methodHandleUnreflected = MethodHandles.lookup().unreflect(methodAccessible);
methodPrimitive = InvocationBenchmark.class.getDeclaredMethod("methodPrimitive", int.class, int.class, int.class, int.class);
methodAccessiblePrimitive = InvocationBenchmark.class.getDeclaredMethod("methodPrimitive", int.class, int.class, int.class, int.class);
methodAccessiblePrimitive.setAccessible(true);
methodHandlePrimitive = MethodHandles.lookup().findVirtual(InvocationBenchmark.class, "methodPrimitive",
MethodType.methodType(int.class, int.class, int.class, int.class, int.class));
methodHandleUnreflectedPrimitive = MethodHandles.lookup().unreflect(methodAccessiblePrimitive);
methodAccessiblePrivate = Access.class.getDeclaredMethod("method", String.class, String.class, String.class, String.class);
methodAccessiblePrivate.setAccessible(true);
methodHandleUnreflectedPrivate = MethodHandles.lookup().unreflect(methodAccessiblePrivate);
}
@Benchmark
public Object normal() throws Exception {
return method(s1, s2, s3, s4);
}
@Benchmark
public Object reflection() throws Exception {
return method.invoke(this, s1, s2, s3, s4);
}
@Benchmark
public Object reflectionAccessible() throws Exception {
return methodAccessible.invoke(this, s1, s2, s3, s4);
}
@Benchmark
public Object handle() throws Throwable {
return methodHandle.invoke(this, s1, s2, s3, s4);
}
@Benchmark
public Object handleExact() throws Throwable {
return (String) methodHandle.invokeExact(this, s1, s2, s3, s4);
}
@Benchmark
public Object handleUnreflectedExact() throws Throwable {
return (String) methodHandleUnreflected.invokeExact(this, s1, s2, s3, s4);
}
@Benchmark
public int primitive() {
return methodPrimitive(i1, i2, i3, i4);
}
@Benchmark
public int reflectionPrimitive() throws Throwable {
return (int) methodPrimitive.invoke(this, i1, i2, i3, i4);
}
@Benchmark
public int reflectionAccessiblePrimitive() throws Throwable {
return (int) methodAccessiblePrimitive.invoke(this, i1, i2, i3, i4);
}
@Benchmark
public int handlePrimitive() throws Throwable {
return (int) methodHandlePrimitive.invoke(this, i1, i2, i3, i4);
}
@Benchmark
public int handlePrimitiveBoxed() throws Throwable {
return (Integer) methodHandlePrimitive.invoke(this, Integer.valueOf(i1), Integer.valueOf(i2), Integer.valueOf(i3), Integer.valueOf(i4));
}
@Benchmark
public int handlePrimitiveExact() throws Throwable {
return (int) methodHandlePrimitive.invokeExact(this, i1, i2, i3, i4);
}
@Benchmark
public Object handleUnreflectedPrimitiveExact() throws Throwable {
return (int) methodHandleUnreflectedPrimitive.invokeExact(this, i1, i2, i3, i4);
}
@Benchmark
public Object privateNormal() throws Exception {
return Access.INSTANCE.method(s1, s2, s3, s4); // accessor method indirection
}
@Benchmark
public Object reflectionAccessiblePrivate() throws Exception {
return methodAccessiblePrivate.invoke(Access.INSTANCE, s1, s2, s3, s4);
}
@Benchmark
public Object handleUnreflectedExactPrivate() throws Throwable {
return (String) methodHandleUnreflectedPrivate.invokeExact(Access.INSTANCE, s1, s2, s3, s4);
}
@Benchmark
public Object handleInline() throws Throwable {
return METHOD_HANDLE_INLINE.invoke(this, s1, s2, s3, s4);
}
@Benchmark
public Object handleExactInline() throws Throwable {
return (String) METHOD_HANDLE_INLINE.invokeExact(this, s1, s2, s3, s4);
}
@Benchmark
public Object handleUnreflectedExactInline() throws Throwable {
return (String) METHOD_HANDLE_UNREFLECTED_INLINE.invokeExact(this, s1, s2, s3, s4);
}
@Benchmark
public int handlePrimitiveInline() throws Throwable {
return (int) METHOD_HANDLE_PRIMITIVE_INLINE.invoke(this, i1, i2, i3, i4);
}
@Benchmark
public int handlePrimitiveBoxedInline() throws Throwable {
return (Integer) METHOD_HANDLE_PRIMITIVE_INLINE.invoke(this, Integer.valueOf(i1), Integer.valueOf(i2), Integer.valueOf(i3), Integer.valueOf(i4));
}
@Benchmark
public int handlePrimitiveExactInline() throws Throwable {
return (int) METHOD_HANDLE_UNREFLECTED_PRIMITIVE_INLINE.invokeExact(this, i1, i2, i3, i4);
}
@Benchmark
public int handleUnreflectedPrimitiveExactInline() throws Throwable {
return (int) METHOD_HANDLE_UNREFLECTED_PRIMITIVE_INLINE.invokeExact(this, i1, i2, i3, i4);
}
@Benchmark
public Object handleUnreflectedExactPrivateInline() throws Throwable {
return (String) METHOD_HANDLE_UNREFLECTED_PRIVATE_INLINE.invokeExact(Access.INSTANCE, s1, s2, s3, s4);
}
}
package benchmark;
import org.openjdk.jmh.annotations.*;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class LookupBenchmark {
private String name = "method";
private MethodHandles.Lookup lookup;
private MethodType methodType;
private Class<?> returnType = void.class, declaringType = LookupBenchmark.class;
void method() {
/* empty */
}
@Setup
public void setUp() throws Exception {
lookup = MethodHandles.lookup();
methodType = MethodType.methodType(void.class);
}
@Benchmark
public Method reflection() throws Exception {
return declaringType.getDeclaredMethod(name);
}
@Benchmark
public MethodHandle handle() throws Exception {
return MethodHandles.lookup().findVirtual(declaringType, name, MethodType.methodType(returnType));
}
@Benchmark
public MethodHandle handlePreLookedUp() throws Exception {
return lookup.findVirtual(declaringType, name, methodType);
}
}
Benchmark Mode Cnt Score Error Units
b.FieldBenchmark.normal avgt 20 2,558 ± 0,031 ns/op
b.FieldBenchmark.primitive avgt 20 2,116 ± 0,092 ns/op
b.FieldBenchmark.privateNormal avgt 20 2,514 ± 0,016 ns/op
b.FieldBenchmark.reflection avgt 20 5,855 ± 0,164 ns/op
b.FieldBenchmark.reflectionAccessible avgt 20 5,515 ± 0,013 ns/op
b.FieldBenchmark.reflectionAccessiblePrimitive avgt 20 5,742 ± 0,033 ns/op
b.FieldBenchmark.reflectionAccessiblePrivate avgt 20 5,484 ± 0,089 ns/op
b.FieldBenchmark.reflectionAccessibleSpecializedPrimitive avgt 20 5,850 ± 0,050 ns/op
b.FieldBenchmark.reflectionPrimitive avgt 20 5,783 ± 0,131 ns/op
b.FieldBenchmark.reflectionSpecializedPrimitive avgt 20 5,765 ± 0,030 ns/op
b.FieldBenchmark.handle avgt 20 6,367 ± 0,167 ns/op
b.FieldBenchmark.handleExact avgt 20 7,154 ± 0,128 ns/op
b.FieldBenchmark.handleInline avgt 20 5,289 ± 0,143 ns/op
b.FieldBenchmark.handleExactInline avgt 20 2,523 ± 0,015 ns/op
b.FieldBenchmark.handlePrimitive avgt 20 5,545 ± 0,061 ns/op
b.FieldBenchmark.handleExactPrimitive avgt 20 5,558 ± 0,043 ns/op
b.FieldBenchmark.handlePrimitiveInline avgt 20 2,043 ± 0,055 ns/op
b.FieldBenchmark.handleExactPrimitiveInline avgt 20 2,023 ± 0,029 ns/op
b.FieldBenchmark.handleUnreflected avgt 20 6,151 ± 0,040 ns/op
b.FieldBenchmark.handleUnreflectedExact avgt 20 7,097 ± 0,025 ns/op
b.FieldBenchmark.handleUnreflectedInline avgt 20 5,314 ± 0,067 ns/op
b.FieldBenchmark.handleUnreflectedExactInline avgt 20 2,513 ± 0,005 ns/op
b.FieldBenchmark.handleUnreflectedPrimitive avgt 20 5,643 ± 0,047 ns/op
b.FieldBenchmark.handleUnreflectedExactPrimitive avgt 20 5,544 ± 0,035 ns/op
b.FieldBenchmark.handleUnreflectedPrimitiveInline avgt 20 2,047 ± 0,026 ns/op
b.FieldBenchmark.handleUnreflectedExactPrimitiveInline avgt 20 2,013 ± 0,015 ns/op
b.FieldBenchmark.handleUnreflectedPrivate avgt 20 7,249 ± 0,053 ns/op
b.FieldBenchmark.handleUnreflectedPrivateInline avgt 20 2,558 ± 0,032 ns/op
# Run on Windows 8.1, x86-64 with Java1.8.0_25
Benchmark Mode Cnt Score Error Units
b.InvocationBenchmark.normal avgt 20 28,364 ± 1,378 ns/op
b.InvocationBenchmark.primitive avgt 20 2,469 ± 0,011 ns/op
b.InvocationBenchmark.privateNormal avgt 20 27,122 ± 0,158 ns/op
b.InvocationBenchmark.reflection avgt 20 31,026 ± 0,213 ns/op
b.InvocationBenchmark.reflectionPrimitive avgt 20 29,537 ± 2,152 ns/op
b.InvocationBenchmark.reflectionAccessible avgt 20 30,420 ± 0,216 ns/op
b.InvocationBenchmark.reflectionAccessiblePrimitive avgt 20 28,582 ± 1,160 ns/op
b.InvocationBenchmark.reflectionAccessiblePrivate avgt 20 33,566 ± 3,674 ns/op
b.InvocationBenchmark.handle avgt 20 32,195 ± 2,448 ns/op
b.InvocationBenchmark.handleExact avgt 20 36,736 ± 0,908 ns/op
b.InvocationBenchmark.handleInline avgt 20 33,549 ± 3,397 ns/op
b.InvocationBenchmark.handleExactInline avgt 20 27,241 ± 0,114 ns/op
b.InvocationBenchmark.handlePrimitive avgt 20 8,525 ± 0,055 ns/op
b.InvocationBenchmark.handlePrimitiveBoxed avgt 20 10,443 ± 0,370 ns/op
b.InvocationBenchmark.handlePrimitiveExact avgt 20 8,237 ± 0,025 ns/op
b.InvocationBenchmark.handlePrimitiveInline avgt 20 2,467 ± 0,006 ns/op
b.InvocationBenchmark.handlePrimitiveBoxedInline avgt 20 9,966 ± 0,036 ns/op
b.InvocationBenchmark.handlePrimitiveExactInline avgt 20 2,462 ± 0,007 ns/op
b.InvocationBenchmark.handleUnreflectedExact avgt 20 30,176 ± 0,239 ns/op
b.InvocationBenchmark.handleUnreflectedExactPrivate avgt 20 29,882 ± 0,152 ns/op
b.InvocationBenchmark.handleUnreflectedPrimitiveExact avgt 20 8,765 ± 0,250 ns/op
b.InvocationBenchmark.handleUnreflectedExactInline avgt 20 28,594 ± 3,065 ns/op
b.InvocationBenchmark.handleUnreflectedExactPrivateInline avgt 20 39,853 ± 6,530 ns/op
b.InvocationBenchmark.handleUnreflectedPrimitiveExactInline avgt 20 2,539 ± 0,035 ns/op
# Run on Windows 8.1, x86-64 with Java1.8.0_25
Benchmark Mode Cnt Score Error Units
b.LookupBenchmark.reflection avgt 20 151,434 ± 15,701 ns/op
b.LookupBenchmark.handle avgt 20 913,461 ± 144,966 ns/op
b.LookupBenchmark.handlePreLookedUp avgt 20 784,532 ± 105,980 ns/op
# Run on Windows 8.1, x86-64 with Java1.8.0_25
@the8472
Copy link

the8472 commented Mar 4, 2015

MHs should be assigned to static final fields so the JIT can inline them.

In some circumstances instance final fields work too if -XX:+TrustFinalNonStaticFields is enabled and the compiler can link that instance to another constant.

@raphw
Copy link
Author

raphw commented Mar 6, 2015

You are right. However, soring a method handle in a static field takes away the possibility of determining a method handle dynamically. This way, you could for example not use the handles in order to avoid the costs of reflection. Only when invoking methods with primitive values, exact invocation can be faster than reflection thanks to the synthetic descriptors and the avoided boxing that come with them.

@deepnighttwo
Copy link

where can I find the org.openjdk.jmh.annotation jar for JDK?

@UnLegitCode
Copy link

where can I find the org.openjdk.jmh.annotation jar for JDK?

Here is a link to the repository, clone it for yourself, and then build yourself the necessary module

https://github.com/openjdk/jmh/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment