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@openjdk.java.net
http://mail.openjdk.java.net/mailman/listinfo/mlvm-dev

_______________________________________________
mlvm-dev mailing list
mlvm-dev@openjdk.java.net
http://mail.openjdk.java.net/mailman/listinfo/mlvm-dev

Reply via email to