Saturday 10 May 2014

Which is faster in Java, Reflection or MethodHandle?


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 Call0.1ns
Reflection4ns
MethodHandle0.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.


Unreflect87ns
Lookup then bind105ns
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;
    }
}



No comments:

Post a Comment