Why is LambdaMetafactory 10% slower than a static MethodHandle but 80% faster than a non-static MethodHandle?

Vladimir Ivanov vladimir.x.ivanov at oracle.com
Mon Feb 19 14:08:21 UTC 2018


>> 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. 
> Ah, so it's unlikely that a future JDK version could eliminate
> that 10% difference between LambdaMetafactory and staticMethodHandle?

Yes, that's correct.

>> But comparing that with nonStaticMethodHandle is not fair: there's no 
>> inlining happening there. 
> Agreed.
> 
> However, for java framework developers,
> it would be really useful to have inlining for non-static method handles 
> too (see Charles's thread),
> because - unlike JVM language developers - we can't use static method 
> handles and don't want to use code generation.

Though inlining is desireable, benefits quickly diminish with the number 
of cases. (For example, C2 only inlines up to 2 targets and only in case 
of bimorphic call site - only 2 receiver classes have been ever seen.)

With non-constant method handles it's even worse: just by looking at the 
call site we can't say anything about what will be called (and how!) 
except its signature (reified as MethodType instance at runtime).

There were some discussions about implementing value profiling for MH 
invokers (invoke()/invokeExact()), but it can only benefit cases where 
the same MethodHandle instance is used always/most of the time.

I seriously doubt it scales well to the use cases you have in mind (like 
JPA/JAXB).

Best regards,
Vladimir Ivanov

> For example, if a JPA or JAXB implementation did use a static fields,
> the code to call methods on a domain hierarchy of classes would look 
> like this:
> 
> public final class MyAccessors {
> 
>      private static final MethodHandle handle1; // Person.getName()
>      private static final MethodHandle handle2; // Person.getAge()
>      private static final MethodHandle handle3; // Company.getName()
>      private static final MethodHandle handle4; // Company.getAddress()
>      private static final MethodHandle handle5; // ...
>      private static final MethodHandle handle6;
>      private static final MethodHandle handle7;
>      private static final MethodHandle handle8;
>      private static final MethodHandle handle9;
>      ...
>      private static final MethodHandle handle1000;
> 
> }
> 
> And furthermore, it would break down with domain hierarchies
> that have more than 1000 getters/setters.
> 
> 
> With kind regards,
> Geoffrey De Smet
> 
> On 19/02/18 13:00, Vladimir Ivanov 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
>>
>>     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.Benchmark;
>>> import org.openjdk.jmh.annotations.BenchmarkMode;
>>> import org.openjdk.jmh.annotations.Fork;
>>> import org.openjdk.jmh.annotations.Measurement;
>>> import org.openjdk.jmh.annotations.Mode;
>>> import org.openjdk.jmh.annotations.OutputTimeUnit;
>>> import org.openjdk.jmh.annotations.Scope;
>>> import org.openjdk.jmh.annotations.State;
>>> 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 = name;
>>>          }
>>>
>>>          public String getName() {
>>>              return name;
>>>          }
>>>
>>>      }
>>>
>>> }
>>>
>>>
>>> With kind regards,
>>> Geoffrey De Smet
>>>
>>> _______________________________________________
>>> 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