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 23:14:42 UTC 2018


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

> Also, does that mean if we try to pollute the LambdaMetafactory (e.g. by 
> 3 different function objects) to prevent inline, we are likely to see 
> similar performance :)

Yes, performance is on a par with polluted profile. The benchmark [1] 
measures non-inlined case for invokeinterface and MH.invokeBasic (3 
invocations/iter):

   LMF._4_lmf_fs    20.020 ± 0.635  ns/op
   LMF._4_lmf_mhs   18.360 ± 0.181  ns/op

Best regards,
Vladimir Ivanov

[1] http://cr.openjdk.java.net/~vlivanov/misc/LMF.java

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


More information about the mlvm-dev mailing list