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 12:00:43 UTC 2018


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