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