Why is LambdaMetafactory 10% slower than a static MethodHandle but 80% faster than a non-static MethodHandle?
Wenlei Xie
wenlei.xie at gmail.com
Mon Feb 19 20:43:03 UTC 2018
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?
Best,
Wenlei
On Mon, Feb 19, 2018 at 12:36 PM, Wenlei Xie <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
> > 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
>>>
>>> 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
>>>>
>>>
>> _______________________________________________
>> mlvm-dev mailing list
>> mlvm-dev at openjdk.java.net
>> http://mail.openjdk.java.net/mailman/listinfo/mlvm-dev
>>
>
>
>
> --
> Best Regards,
> Wenlei Xie (谢文磊)
>
> Email: wenlei.xie at gmail.com
>
--
Best Regards,
Wenlei Xie (谢文磊)
Email: wenlei.xie at gmail.com
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.openjdk.java.net/pipermail/mlvm-dev/attachments/20180219/b0a930b9/attachment-0001.html>
More information about the mlvm-dev
mailing list