Why is LambdaMetafactory 10% slower than a static MethodHandle but 80% faster than a non-static MethodHandle?
Remi Forax
forax at univ-mlv.fr
Tue Feb 20 13:23:23 UTC 2018
----- Mail original -----
> De: "Vladimir Ivanov" <vladimir.x.ivanov at oracle.com>
> À: "Wenlei Xie" <wenlei.xie at gmail.com>, "Da Vinci Machine Project" <mlvm-dev at openjdk.java.net>
> Envoyé: Mardi 20 Février 2018 00:14:42
> Objet: Re: Why is LambdaMetafactory 10% slower than a static MethodHandle but 80% faster than a non-static MethodHandle?
>> Sorry if it's a dumb question, but why nonStaticMethodHandle cannot get
>> inlined here? -- In the benchmark it's always the same line with the
>> same final MethodHandle variable, can JIT based on some profiling info
>> to inline it (similar to the function object generated by
>> LambdaMetafactory). -- Or it cannot sine InvokeExact's
>> PolymorphicSignature makes it quite special?
>
> Yes, method handle invokers are special and ordinary type profiling
> (class-based) doesn't work for them.
>
> There was an idea to implement value profiling for MH invokers: record
> individual MethodHandle instances observed at invoker call sites and use
> that to guide devirtualizaiton & inlining decisions. But it looked way
> too specialized to be beneficial in practice.
Here is a code that does exactly that,
https://gist.github.com/forax/7bf08669f58804991fd45656a671c381
[...]
> Best regards,
> Vladimir Ivanov
Rémi
>> On Mon, Feb 19, 2018 at 4:00 AM, Vladimir Ivanov
>> <vladimir.x.ivanov at oracle.com <mailto:vladimir.x.ivanov at oracle.com>> wrote:
>>
>> Geoffrey,
>>
>> In both staticMethodHandle & lambdaMetafactory Dog::getName is
>> inlined, but using different mechanisms.
>>
>> In staticMethodHandle target method is statically known [1], but in
>> case of lambdaMetafactory [2] compiler has to rely on profiling info
>> to devirtualize Function::apply(). The latter requires exact type
>> check on the receiver at runtime and that explains the difference
>> you are seeing.
>>
>> But comparing that with nonStaticMethodHandle is not fair: there's
>> no inlining happening there.
>>
>> If you want a fair comparison, then you have to measure with
>> polluted profile so no inlining happens. In that case [3] non-static
>> MethodHandles are on par (or even slightly faster):
>>
>> LMF._4_lmf_fs avgt 10 20.020 ± 0.635 ns/op
>> LMF._4_lmf_mhs avgt 10 18.360 ± 0.181 ns/op
>>
>> (scores for 3 invocations in a row.)
>>
>> Best regards,
>> Vladimir Ivanov
>>
>> [1] 715 126 b org.lmf.LMF::_1_staticMethodHandle (11 bytes)
>> ...
>> @ 37
>> java.lang.invoke.DirectMethodHandle$Holder::invokeVirtual (14
>> bytes) force inline by annotation
>> @ 1 java.lang.invoke.DirectMethodHandle::internalMemberName
>> (8 bytes) force inline by annotation
>> @ 10 org.lmf.LMF$Dog::getName (5 bytes) accessor
>>
>>
>>
>>
>> [2] 678 117 b org.lmf.LMF::_2_lambdaMetafactory (14 bytes)
>> @ 8 org.lmf.LMF$$Lambda$37/552160541::apply (8 bytes) inline (hot)
>> \-> TypeProfile (6700/6700 counts) = org/lmf/LMF$$Lambda$37
>> @ 4 org.lmf.LMF$Dog::getName (5 bytes) accessor
>>
>>
>> [3] http://cr.openjdk.java.net/~vlivanov/misc/LMF.java
>> <http://cr.openjdk.java.net/~vlivanov/misc/LMF.java>
>>
>> static Function make() throws Throwable {
>> CallSite site = LambdaMetafactory.metafactory(LOOKUP,
>> "apply",
>> MethodType.methodType(Function.class),
>> MethodType.methodType(Object.class, Object.class),
>> LOOKUP.findVirtual(Dog.class, "getName",
>> MethodType.methodType(String.class)),
>> MethodType.methodType(String.class, Dog.class));
>> return (Function) site.getTarget().invokeExact();
>> }
>>
>> private Function[] fs = new Function[] {
>> make(), make(), make()
>> };
>>
>> private MethodHandle[] mhs = new MethodHandle[] {
>> nonStaticMethodHandle,
>> nonStaticMethodHandle,
>> nonStaticMethodHandle
>> };
>>
>> @Benchmark
>> public Object _4_lmf_fs() throws Throwable {
>> Object r = null;
>> for (Function f : fs {
>> r = f.apply(dogObject);
>> }
>> return r;
>> }
>>
>> @Benchmark
>> public Object _4_lmf_mh() throws Throwable {
>> Object r = null;
>> for (MethodHandle mh : mhs) {
>> r = mh.invokeExact(dogObject);
>> }
>> return r;
>>
>> }
>>
>> On 2/19/18 1:42 PM, Geoffrey De Smet wrote:
>>
>> Hi guys,
>>
>> I ran the following JMH benchmark on JDK 9 and JDK 8.
>> Source code and detailed results below.
>>
>> Benchmark on JDK 9 Score
>> staticMethodHandle 2.770
>> lambdaMetafactory 3.052 // 10% slower
>> nonStaticMethodHandle 5.250 // 90% slower
>>
>> Why is LambdaMetafactory 10% slower than a static MethodHandle
>> but 80% faster than a non-static MethodHandle?
>>
>>
>> Source code (copy paste ready)
>> ====================
>>
>> import java.lang.invoke.CallSite;
>> import java.lang.invoke.LambdaMetafactory;
>> import java.lang.invoke.MethodHandle;
>> import java.lang.invoke.MethodHandles;
>> import java.lang.invoke.MethodType;
>> import java.util.concurrent.TimeUnit;
>> import java.util.function.Function;
>>
>> import org.openjdk.jmh.annotations.Be
>> <http://org.openjdk.jmh.annotations.Be>nchmark;
>> import org.openjdk.jmh.annotations.Be
>> <http://org.openjdk.jmh.annotations.Be>nchmarkMode;
>> import org.openjdk.jmh.annotations.Fo
>> <http://org.openjdk.jmh.annotations.Fo>rk;
>> import org.openjdk.jmh.annotations.Me
>> <http://org.openjdk.jmh.annotations.Me>asurement;
>> import org.openjdk.jmh.annotations.Mo
>> <http://org.openjdk.jmh.annotations.Mo>de;
>> import org.openjdk.jmh.annotations.OutputTimeUnit;
>> import org.openjdk.jmh.annotations.Sc
>> <http://org.openjdk.jmh.annotations.Sc>ope;
>> import org.openjdk.jmh.annotations.St
>> <http://org.openjdk.jmh.annotations.St>ate;
>> import org.openjdk.jmh.annotations.Warmup;
>>
>> //Benchmark on JDK 9 Mode Cnt Score Error Units
>> //staticMethodHandle avgt 30 2.770 ± 0.023 ns/op // Baseline
>> //lambdaMetafactory avgt 30 3.052 ± 0.004 ns/op // 10%
>> slower
>> //nonStaticMethodHandle avgt 30 5.250 ± 0.137 ns/op // 90%
>> slower
>>
>> //Benchmark on JDK 8 Mode Cnt Score Error Units
>> //staticMethodHandle avgt 30 2.772 ± 0.022 ns/op // Baseline
>> //lambdaMetafactory avgt 30 3.060 ± 0.007 ns/op // 10%
>> slower
>> //nonStaticMethodHandle avgt 30 5.037 ± 0.022 ns/op // 81%
>> slower
>>
>> @Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
>> @Measurement(iterations = 10, time = 1, timeUnit = TimeUnit.SECONDS)
>> @Fork(3)
>> @BenchmarkMode(Mode.AverageTime)
>> @OutputTimeUnit(TimeUnit.NANOSECONDS)
>> @State(Scope.Thread)
>> public class LamdaMetafactoryWeirdPerformance {
>>
>> //
>> ************************************************************************
>> // Set up of the 3 approaches.
>> //
>> ************************************************************************
>>
>> // Unusable for Java framework developers. Only usable by
>> JVM language developers. Baseline.
>> private static final MethodHandle staticMethodHandle;
>>
>> // Usuable for Java framework developers. 30% slower
>> private final Function lambdaMetafactoryFunction;
>>
>> // Usuable for Java framework developers. 100% slower
>> private final MethodHandle nonStaticMethodHandle;
>>
>> static {
>> // Static MethodHandle setup
>> try {
>> staticMethodHandle = MethodHandles.lookup()
>> .findVirtual(Dog.class, "getName",
>> MethodType.methodType(String.class))
>>
>> .asType(MethodType.methodType(Object.class, Object.class));
>> } catch (NoSuchMethodException |
>> IllegalAccessException e) {
>> throw new IllegalStateException(e);
>> }
>> }
>>
>> public LamdaMetafactoryWeirdPerformance() {
>> try {
>> MethodHandles.Lookup lookup = MethodHandles.lookup();
>>
>> // LambdaMetafactory setup
>> CallSite site = LambdaMetafactory.metafactory(lookup,
>> "apply",
>> MethodType.methodType(Function.class),
>> MethodType.methodType(Object.class,
>> Object.class),
>> lookup.findVirtual(Dog.class, "getName",
>> MethodType.methodType(String.class)),
>> MethodType.methodType(String.class,
>> Dog.class));
>> lambdaMetafactoryFunction = (Function)
>> site.getTarget().invokeExact();
>>
>> // Non-static MethodHandle setup
>> nonStaticMethodHandle = lookup
>> .findVirtual(Dog.class, "getName",
>> MethodType.methodType(String.class))
>>
>> .asType(MethodType.methodType(Object.class, Object.class));
>> } catch (Throwable e) {
>> throw new IllegalStateException(e);
>> }
>> }
>>
>> //
>> ************************************************************************
>> // Benchmark
>> //
>> ************************************************************************
>>
>> private Object dogObject = new Dog("Fido");
>>
>>
>> @Benchmark
>> public Object _1_staticMethodHandle() throws Throwable {
>> return staticMethodHandle.invokeExact(dogObject);
>> }
>>
>> @Benchmark
>> public Object _2_lambdaMetafactory() {
>> return lambdaMetafactoryFunction.apply(dogObject);
>> }
>>
>> @Benchmark
>> public Object _3_nonStaticMethodHandle() throws Throwable {
>> return nonStaticMethodHandle.invokeExact(dogObject);
>> }
>>
>> private static class Dog {
>> private String name;
>>
>> public Dog(String name) {
>> this.name <http://this.name> = name;
>> }
>>
>> public String getName() {
>> return name;
>> }
>>
>> }
>>
>> }
>>
>>
>> With kind regards,
>> Geoffrey De Smet
>>
>> _______________________________________________
>> mlvm-dev mailing list
>> mlvm-dev at openjdk.java.net <mailto:mlvm-dev at openjdk.java.net>
>> http://mail.openjdk.java.net/mailman/listinfo/mlvm-dev
>> <http://mail.openjdk.java.net/mailman/listinfo/mlvm-dev>
>>
>> _______________________________________________
>> mlvm-dev mailing list
>> mlvm-dev at openjdk.java.net <mailto:mlvm-dev at openjdk.java.net>
>> http://mail.openjdk.java.net/mailman/listinfo/mlvm-dev
>> <http://mail.openjdk.java.net/mailman/listinfo/mlvm-dev>
>>
>>
>>
>>
>> --
>> Best Regards,
>> Wenlei Xie (谢文磊)
>>
>> Email: wenlei.xie at gmail.com <mailto:wenlei.xie at gmail.com>
> _______________________________________________
> mlvm-dev mailing list
> mlvm-dev at openjdk.java.net
> http://mail.openjdk.java.net/mailman/listinfo/mlvm-dev
More information about the mlvm-dev
mailing list