2014 has been shaping up to be an awesome year so far. London is a great place to work, especially when it has given me a lot of opportunities to figure out how to make Java run faster.
For Reflection vs MethodHandles, lets jump straight to some numbers. The code used to create these benchmark numbers is included at the end.
Direct Method Call | 0.1ns |
Reflection | 4ns |
MethodHandle | 0.1ns - 110ns |
Measurements taken running Java 7 on a shiny OSX laptop
Huh? Why the huge range of values for MethodHandle? The reason is that it depends on how the MethodHandle is created and used. Being a richer API than reflection, it can either run like a farm pig ready for Christmas or become optimised down to being the same as a direct method call. Awesome. Thus this post shows how to avoid the costs, and stream line the use of MethodHandle.
Unreflect | 87ns |
Lookup then bind | 105ns |
Unreflect then bind | 111ns |
Invoke exact | 11ns |
Unreflect invokeExact | 6ns |
Store MethodHandle in private static final field | 0.1ns |
Method in private static final field | 4ns |
These numbers show that some care needs to be taken with MethodHandle, but treat it well and store it in a private static final field and then Hotspot can optimise the reflection out entirely. Pretty awesome.
package reports.signals;
import com.softwaremosaic.junit.JUnitMosaicRunner;
import com.softwaremosaic.junit.annotations.Benchmark;
import org.junit.runner.RunWith;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
*
*/
@RunWith(JUnitMosaicRunner.class)
public class SignalManagerExperimentalTest {
private int i=0;
public void m() {
i += 1;
}
private static final int NUMITS = 10000;
/*
6.61ns per call
4.95ns per call
4.08ns per call
3.95ns per call
3.93ns per call
3.88ns per call
*/
@Benchmark( value=1000, durationResultMultiplier = 1.0/NUMITS )
public int fooRef() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Method f = this.getClass().getMethod( "m" );
for ( int i=0;i
f.invoke( this );
}
return i;
}
private static final Method pf = getF();
private static Method getF() {
try {
return SignalManagerExperimentalTest.class.getMethod( "m" );
} catch ( NoSuchMethodException e ) {
e.printStackTrace();
}
return null;
}
/*
7.27ns per call
4.75ns per call
3.79ns per call
3.72ns per call
3.71ns per call
4.29ns per call
*/
@Benchmark( value=1000, durationResultMultiplier = 1.0/NUMITS )
public int fooRefPF() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
for ( int i=0;i
pf.invoke( this );
}
return i;
}
/*
0.09ns per call
0.12ns per call
0.10ns per call
0.11ns per call
0.10ns per call
0.10ns per call
*/
@Benchmark( value=1000, durationResultMultiplier = 1.0/NUMITS )
public int fooDirect() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
for ( int i=0;i
m();
}
return i;
}
/*
unreflect
87.07ns per call
87.38ns per call
87.39ns per call
87.98ns per call
86.89ns per call
88.20ns per call
*/
@Benchmark( value=1000, durationResultMultiplier = 1.0/NUMITS )
public int unreflect() throws Throwable {
Method f = this.getClass().getMethod( "m" );
MethodHandle h = MethodHandles.lookup().unreflect( f );
for ( int i=0;i
h.invoke(this);
}
return i;
}
/*
lookup then bind
110.45ns per call
106.48ns per call
106.04ns per call
106.15ns per call
105.20ns per call
105.13ns per call
*/
@Benchmark( value=1000, durationResultMultiplier = 1.0/NUMITS )
public int lookupBind() throws Throwable {
MethodHandle h = MethodHandles.lookup().bind( this, "m", MethodType.methodType(Void.TYPE) );
for ( int i=0;i
h.invoke();
}
return i;
}
/*
unreflect then bind
112.95ns per call
110.62ns per call
112.70ns per call
112.77ns per call
111.10ns per call
111.64ns per call
*/
@Benchmark( value=1000, durationResultMultiplier = 1.0/NUMITS )
public int unreflectBind() throws Throwable {
Method f = this.getClass().getMethod( "m" );
MethodHandle h = MethodHandles.lookup().unreflect( f ).bindTo( this );
for ( int i=0;i
h.invoke();
}
return i;
}
/*
invoke exact
12.65ns per call
12.03ns per call
11.17ns per call
10.46ns per call
10.36ns per call
10.18ns per call
*/
@Benchmark( value=1000, durationResultMultiplier = 1.0/NUMITS )
public int invokeExact() throws Throwable {
MethodHandle h = MethodHandles.lookup().bind( this, "m", MethodType.methodType(Void.TYPE) );
for ( int i=0;i
h.invokeExact();
}
return i;
}
/*
unreflect invokeExact
7.49ns per call
7.12ns per call
6.75ns per call
6.72ns per call
6.22ns per call
5.92ns per call
*/
@Benchmark( value=1000, durationResultMultiplier = 1.0/NUMITS )
public int unreflectInvokeExact() throws Throwable {
Method f = this.getClass().getMethod( "m" );
MethodHandle h = MethodHandles.lookup().unreflect( f );
for ( int i=0;i
h.invokeExact(this);
}
return i;
}
private static final MethodHandle mh = lookupViaFindVirtual();
private static MethodHandle lookupViaFindVirtual() {
try {
return MethodHandles.lookup().findVirtual( SignalManagerExperimentalTest.class, "m" , MethodType.methodType(Void.TYPE));
} catch ( Throwable e ) {
e.printStackTrace();
return null;
}
}
/*
0.12ns per call
0.09ns per call
0.12ns per call
0.11ns per call
0.10ns per call
0.10ns per call
*/
@Benchmark( value=1000, durationResultMultiplier = 1.0/NUMITS )
public int fooHandleSF() throws Throwable {
for ( int i=0;i
mh.invokeExact( this );
}
return i;
}
}