MethodHandle performance in Java 7
Overview
A new feature in Java 7 provides a MethodHandle which "is a typed, directly executable reference to an underlying method, constructor, field, or similar low-level operation, with optional transformations of arguments or return values"This supports currying methods and many other features lambda based languages take for granted.
Note: this method compares invoke() in reflections and MethodHandles. I couldn't get invokeExact to work when returning a primitive (it got confused between int and Integer) However another benchmark Using Method Handles by Andrew Till indicated using invokeExact is much faster than using Method.invoke() with reflections. Thank you wensveen for the link.
Example
Say you have two methodpublic static int multiply(int i, int j) { return i * j; } public static int subtract(int i, int j) { return i - j; }With MethodHandle you can not only get a reference to the Method (like in reflection) but you can construct new MethodHandles. For example, you can bind the Object concerned or one of the arguments.
final Lookup lookup = lookup(); MethodHandle multiply = lookup.findStatic(MethodHandleMain.class, "multiply", methodType(int.class, int.class, int.class)); MethodHandle quadruple = insertArguments(multiply, 1, 4); System.out.println(multiply.invoke(3, 2)); // prints 6 System.out.println(quadruple.invoke(5)); // prints 20 MethodHandle subtract = lookup.findStatic(MethodHandleMain.class, "subtract", methodType(int.class, int.class, int.class)); MethodHandle subtractFromFour = insertArguments(subtract, 0, 4); MethodHandle fourLess = insertArguments(subtract, 1, 4); System.out.println(subtract.invoke(10, 5)); // prints 5 System.out.println(subtractFromFour.invoke(10)); // prints -6 System.out.println(fourLess.invoke(10)); // prints 6
Performance
This is very cool, but how does it perform? ;)There is a lot of potential for it to perform very well and the interface is cleaner than plain reflections however when comparing the alternatives, it doesn't perform as well as the alternatives in Java 7 (update 0)
Call method | Average time per call |
---|---|
Direct Method calls | 31 ns |
Reflections | 353 ns |
MethodHandle.invoke() | 5,378 ns |
It would make a lot more sense if it also had syntax support in the compiler, i.e. something the compiler might check, e.g.:
ReplyDeletefinal MethodHandle add = Example.class.method.add(int,int);
assert 8 == add.invoke(3, 5);
@Loki, I believe that is what they are looking at for Java 8. Performance is a known issue, and they are looking to improve it. In theory it can be faster than reflection.
ReplyDeleteSomething like
int(int,int) add = Example.add;
assert 8 = add(3, 5);
Another blog I found claims much faster performance, albeit for a very simple operation: http://andrewtill.blogspot.com/2011/08/using-method-handles.html
ReplyDeleteSo when the performance kinks are worked out, and the Java 8 syntax is done, this API will be great to work with. (Almost as nice as the C# syntax *ducks*)
When measuring invocation via reflection do you take the obvious cachable method lookup into account or is the measure for method invocation only?
ReplyDeleteComparing virtual with static invocation is not fair at all ;)
@Sven, the code is attached at the end. I cache as much as possible and only measure the invocation times.
ReplyDeleteIn theory, it should be possible to make a reflective call as fast as a direct call.
Hi Peter
ReplyDeleteThanks for the link-back. I retried your sample code and my own just to make sure I hadn't done something silly and got the same results for my own tests and similar results for yours. Maybe having a return value from a method handle is what is causing the poor performance.
I did turn on GC logging and method handles did causing much more GCs than the direct or reflection tests which is suprising. It doesn't look like it is because of Integer creation since all the results look like they would fall within the default Integer cache range. In my test there was no meaningful GC.
It looks like there is some more investigation to be done before the correct use case is found for the current implementation of method handles.
@Andy Till, Thank you for the confirmation. In two presentations I have seen about MethodHandles they mentioned performance being an issue they were still looking at. You may be right that some use cases will work much better than others however I have some hope that is future releases all use cases will perform very well.
ReplyDeleteIt appears this functionality will tie into Java 8's closures (i.e. Java 8 having syntax sugar for this library) I am sure once using it become mainstream it will quickly have all the possible cases sorted.
Hello Peter,
ReplyDeleteJust tried out your benchmark with a subtle adjustment: an int instead of Integer type cast for each method handle call.
The performance implications are less subtle: in my results (with java 7 update 3) method handle calls are faster than direct calls for the number of runs you specified.
Using MethodHandle.invokeExact instead of invoke didn't have any effect on my results of these benchmarks.
When using Integer instead of int as return type for the multiply and subtract methods, performance of method handle is much better than of reflection but worse than of direct calls. However, this is only true when using the original Integer (instead of int) type cast for each method handle call.
All in all it seems that primitive boxing/unboxing is very expensive for method handle calls. Otherwise method handle performance looks very promising.
Cheers, Hans
Hello Hans,
ReplyDeleteThank you for pointing that out. It appears the example I gave is not the best example for MethodHandle. I heard in a talk given by oracle, that they are aware of some performance issues they are working one.
I imagine this feature will be used much mroe in Java 8 as it will have language support. e.g. you can refer a method like "Class.method" instead of having to create method handles dynamically.
Dredging up an old topic, but I just ran across this post and checked to see how well it worked on current versions of Java 7
ReplyDeleteIf you change the method handle version to cast to (int) instead of (Integer), My times went from 5.1 seconds to 37 ns
and direct calls were 36 ns, so basically the same
Do that for reflections (cast to int) and my times dropped from ~515 to ~480. So, an improvement for reflections but not as much.
this was on OSX, java 7 U25, 2.7ghz i7 mac laptop
@DavidBudworth Sorry I missed you last week. Thank you for the update. It is good to know this is something which has been fixed. I suspect MethodHandles will be used a lot in the implementation of closures in Java 8.
ReplyDeleteIt isn't with the return types, MethodHandles work very differently depending on how they are used. I would check out this article as it explains the details better : http://chriskirk.blogspot.com/2014/05/which-is-faster-in-java-reflection-or.html
ReplyDelete