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