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

Geoffrey De Smet ge0ffrey.spam at gmail.com
Mon Feb 19 13:41:32 UTC 2018


Thank you for the insight, Vladimir.

> 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?

Good to know.

> 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.

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