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 21:10:41 UTC 2018



On 2/19/18 11:43 PM, Wenlei Xie wrote:
> Never mind. I miss some points in the previous discussion. Static method 
> handle can get further benefit from JIT:
> 
>  > JIT-compiler extracts method handle instance from static final field 
> (as if it were a constant from class constant pool) and inlines through 
> MH.invokeExact() down to the target method.
> 
> Is an orthogonal optimization with MethodHandle customization?

Yes, they are complementary. LambdaForm customization is applied to 
method handles observed at MH.invokeExact()/invoke() call sites as 
non-constants (in JIT-compiled code). There won't be any customization 
applied (at least, at that particular call site) to a method handle 
coming from a static final field.

Best regards,
Vladimir Ivanov

> On Mon, Feb 19, 2018 at 12:36 PM, Wenlei Xie <wenlei.xie at gmail.com 
> <mailto:wenlei.xie at gmail.com>> wrote:
> 
>     > However, for java framework developers,
>     > it would be really useful to have inlining for non-static method handles too (see Charles's thread),
> 
>     Is the problem that non-static MethodHandle doesn't get customized,
>     or it's because in the benchmark, each time it will use a new
>     MethodHandle from reflection?
> 
>     I remember a MethodHandle will be customized when it was called over
>     a threshold (127 is the default). Thus as long as you are using the
>     same MethodHandle over the time, you will get the performance
>     benefit from customization, right?
> 
> 
> 
> 
>     Best,
>     Wenlei
> 
> 
>     On Mon, Feb 19, 2018 at 5:41 AM, Geoffrey De Smet
>     <ge0ffrey.spam at gmail.com <mailto:ge0ffrey.spam at gmail.com>> wrote:
> 
>         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
>             <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>
> 
> 
> 
> 
> -- 
> Best Regards,
> Wenlei Xie (谢文磊)
> 
> Email: wenlei.xie at gmail.com <mailto:wenlei.xie at gmail.com>


More information about the mlvm-dev mailing list