1

Here I found the following code that shows the difference in perfomance for MethodHandles and Reflection:

@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(3)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class MHOpto {

    private int value = 42;

    private static final Field static_reflective;
    private static final MethodHandle static_unreflect;
    private static final MethodHandle static_mh;

    private static Field reflective;
    private static MethodHandle unreflect;
    private static MethodHandle mh;

    // We would normally use @Setup, but we need to initialize "static final" fields here...
    static {
        try {
            reflective = MHOpto.class.getDeclaredField("value");
            unreflect = MethodHandles.lookup().unreflectGetter(reflective);
            mh = MethodHandles.lookup().findGetter(MHOpto.class, "value", int.class);
            static_reflective = reflective;
            static_unreflect = unreflect; //LINE X!!!
            static_mh = mh;
        } catch (IllegalAccessException | NoSuchFieldException e) {
            throw new IllegalStateException(e);
        }
    }

    @Benchmark
    public int plain() {
        return value;
    }

    @Benchmark
    public int dynamic_reflect() throws InvocationTargetException, IllegalAccessException {
        return (int) reflective.get(this);
    }

    @Benchmark
    public int dynamic_unreflect_invoke() throws Throwable {
        return (int) unreflect.invoke(this);
    }

    @Benchmark
    public int dynamic_unreflect_invokeExact() throws Throwable {
        return (int) unreflect.invokeExact(this);
    }

    @Benchmark
    public int dynamic_mh_invoke() throws Throwable {
        return (int) mh.invoke(this);
    }

    @Benchmark
    public int dynamic_mh_invokeExact() throws Throwable {
        return (int) mh.invokeExact(this);
    }

    @Benchmark
    public int static_reflect() throws InvocationTargetException, IllegalAccessException {
        return (int) static_reflective.get(this);
    }

    @Benchmark
    public int static_unreflect_invoke() throws Throwable {
        return (int) static_unreflect.invoke(this);
    }

    @Benchmark
    public int static_unreflect_invokeExact() throws Throwable {
        return (int) static_unreflect.invokeExact(this);
    }

    @Benchmark
    public int static_mh_invoke() throws Throwable {
        return (int) static_mh.invoke(this);
    }

    @Benchmark
    public int static_mh_invokeExact() throws Throwable {
        return (int) static_mh.invokeExact(this);
    }

}

And these are the results:

Benchmark                             Mode  Cnt  Score   Error  Units
MHOpto.dynamic_mh_invoke              avgt   25  4.393 ± 0.003  ns/op
MHOpto.dynamic_mh_invokeExact         avgt   25  4.394 ± 0.007  ns/op
MHOpto.dynamic_reflect                avgt   25  5.230 ± 0.020  ns/op
MHOpto.dynamic_unreflect_invoke       avgt   25  4.404 ± 0.023  ns/op
MHOpto.dynamic_unreflect_invokeExact  avgt   25  4.397 ± 0.014  ns/op
MHOpto.plain                          avgt   25  1.858 ± 0.002  ns/op
MHOpto.static_mh_invoke               avgt   25  1.862 ± 0.015  ns/op
MHOpto.static_mh_invokeExact          avgt   25  1.859 ± 0.002  ns/op
MHOpto.static_reflect                 avgt   25  4.274 ± 0.011  ns/op
MHOpto.static_unreflect_invoke        avgt   25  1.859 ± 0.002  ns/op
MHOpto.static_unreflect_invokeExact   avgt   25  1.858 ± 0.002  ns/op

What I don't understand is this line of code:

static_unreflect = unreflect;

Is static_unreflect (final) not equal to unreflect (not final)? Then why do they show different results in perfomance? Could anyone explain?

Pavel_K
  • 10,748
  • 13
  • 73
  • 186
  • 1
    Actually, there is a difference between `unreflect` and `static_unreflect`: the latter is defined to be `final`. – Turing85 Jul 02 '19 at 21:07
  • @Turing85 Thank you. I see that. But both variables are set only once - when class is firstly loaded and static block is executed. Why does it matter? – Pavel_K Jul 02 '19 at 21:10
  • Well the `final` may be of importance in this case. Since the reference can never change, it can be optimized. I am not sure whether the hotspot compiler will be able to infer that the `unreflect` reference is effectively final. If it cannot, this may contribute to the better performance – Turing85 Jul 03 '19 at 22:06
  • The measured results do already answer that… – Holger Jul 09 '19 at 17:00
  • @Holger there is a great video from Shipilev about this (it is in Russian, though) [here](https://www.youtube.com/watch?v=ESs0bZw8hsA). There are several times in the talk when he mentions that constant folding happens best when `MethodHandle`s are constants – Eugene Jul 09 '19 at 18:55
  • @Pavel_K it seems that you being from Moscow, will be able to understand that talk, take a look around minute 12 to 13... "...oni constant foldiatsia kogda MethodHandles constanta..." – Eugene Jul 09 '19 at 18:57
  • @Eugene Thank you very much for the interesting link. I've watched it, however, he doesn't explain there the reason pocemu "oni oceni horosho constanholdeatsea". – Pavel_K Jul 09 '19 at 19:16
  • @Pavel_K the reason for me is somehow "understandable", at least I think so. since this is a constant, all the checks against what types must match, if such a method exists, etc etc can be done at compile time - you know, the entire point of "constant folding". it is a _compiler_ optimization after all. Not sure this counts as an answer, though. – Eugene Jul 10 '19 at 08:58
  • Even if your program never changes the `static` field, the optimizer has to consider the possibility of modifications, in an environment with lazy/dynamic class loading and supporting Reflection with access override. In the best case, it could use an optimistic approach, providing an optimized code path with a pre-check ensuring that the optimized path is only entered when the field is unchanged. How much would such a check cost? More or less than the 2.5ns difference between the immutable and the mutable field usage? – Holger Jul 10 '19 at 11:11

1 Answers1

3

Only the static final variant of the MethodHandle is seen as a constant by the JIT, see e.g. ciField:

// Is this field a constant?
//
// Clarification: A field is considered constant if:
//   1. The field is both static and final
//   2. The field is not one of the special static/final
//      non-constant fields.  These are java.lang.System.in
//      and java.lang.System.out.  Abomination.
//
// A field is also considered constant if
// - it is marked @Stable and is non-null (or non-zero, if a primitive) or
// - it is trusted or
// - it is the target field of a CallSite object.
//
// See ciField::initialize_from() for more details.
//
// A user should also check the field value (constant_value().is_valid()), since
// constant fields of non-initialized classes don't have values yet.
bool is_constant() const { return _is_constant; }

And only calls through MethodHandles that are constant are inlined, see CallGenerator::for_method_handle_inline Where it does several checks to see that the receiver is constant like:

Node* receiver = kit.argument(0);
if (receiver->Opcode() == Op_ConP) {
  ...
} else {
  print_inlining_failure(C, callee, jvms->depth() - 1, jvms->bci(),
                         "receiver not constant");
}

This difference makes it so that the call to the static final MethodHandle can be inlined, and is therefore roughly as fast as the plain case.

If you print out inlining information you can see this as well. e.g. you could add something like:

@Fork(jvmArgsAppend="-Xlog:inlining*=trace:inlining-%p-static_mh_invokeExact.txt")

To the benchmark methods.

In the static case you will see the call being inlined:

 @ 17   org.sample.MyBenchmark::static_mh_invokeExact (8 bytes)   force inline by CompileCommand
   @ 4   java.lang.invoke.LambdaForm$MH/0x00000008000f0040::invokeExact_MT (23 bytes)   force inline by annotation
     @ 10   java.lang.invoke.Invokers::checkExactType (17 bytes)   force inline by annotation
       @ 1   java.lang.invoke.MethodHandle::type (5 bytes)
     @ 14   java.lang.invoke.Invokers::checkCustomized (23 bytes)   force inline by annotation
       @ 1   java.lang.invoke.MethodHandleImpl::isCompileConstant (2 bytes)
     @ 19   java.lang.invoke.LambdaForm$MH/0x00000008000f0440::getInt (34 bytes)   force inline by annotation
       @ 7   java.lang.invoke.DirectMethodHandle::fieldOffset (9 bytes)   force inline by annotation
       @ 12   java.lang.invoke.DirectMethodHandle::checkBase (5 bytes)   force inline by annotation
         @ 1   java.util.Objects::requireNonNull (14 bytes)
           @ 8   java.lang.NullPointerException::<init> (5 bytes)   don't inline Throwable constructors
       @ 30   jdk.internal.misc.Unsafe::getInt (0 bytes)   intrinsic

We're inlining all the way to the Unsafe::getInt call (but the important part is that we see @ 19 java.lang.invoke.LambdaForm$MH/0x00000008000f0440::getInt instead of the invokeBasic).

In the dynamic case, you'll at most see:

 @ 17   org.sample.MyBenchmark::dynamic_mh_invokeExact (8 bytes)   force inline by CompileCommand
   @ 4   java.lang.invoke.LambdaForm$MH/0x00000008000f0040::invokeExact_MT (23 bytes)   force inline by annotation
     @ 10   java.lang.invoke.Invokers::checkExactType (17 bytes)   force inline by annotation
       @ 1   java.lang.invoke.MethodHandle::type (5 bytes)
       @ 12   java.lang.invoke.Invokers::newWrongMethodTypeException (36 bytes)   callee is too large
     @ 14   java.lang.invoke.Invokers::checkCustomized (23 bytes)   force inline by annotation
       @ 1   java.lang.invoke.MethodHandleImpl::isCompileConstant (2 bytes)
       @ 19   java.lang.invoke.Invokers::maybeCustomize (28 bytes)   don't inline by annotation
     @ 19   java.lang.invoke.MethodHandle::invokeBasic(L)I (0 bytes)   receiver not constant

I.e. in that case there is still an indirect call through the invokeBasic stub, because "receiver not constant".

Jorn Vernee
  • 31,735
  • 4
  • 76
  • 93
  • so this is specific for MethodHandles turn out? – Eugene Jul 10 '19 at 12:48
  • @Eugene Pretty much. When using e.g. an interface instead, the JIT can also do type profiling (i.e. check which concrete type we're actually seeing) and do speculative inlining based on that, but MethodHandles don't have an equivalent (at least not yet). – Jorn Vernee Jul 10 '19 at 12:55
  • @Eugene One trick you can do is to generate a class per MethodHandle (e.g. with ASM) where the MethodHandle is stuck in the constant pool or a `static final` field, and the class has a getter for the MethodHandle. Where you basically call `o.get().invokeExact(...)` The JIT will type profile the class, do speculative inlining of the getter and at that point the MethodHandle is a constant again, and can be inlinined. – Jorn Vernee Jul 10 '19 at 12:56
  • @Does it mean that in case of MethodHandle Java language doesn't work according to spec? I mean that `static_unreflect` (final) must give the same results as `unreflect `(not final), however it doesn't. According to spec the `final`shows that variable is constant - we can't change it in code after initialization and it mustn't affect performance. – Pavel_K Jul 10 '19 at 13:45
  • 1
    @Pavel_K _"and it mustn't affect performance."_ Do you have a link to that? The result of the MethodHandle calls is `42`, regardless of how fast it's computed, so the result is still the same. – Jorn Vernee Jul 10 '19 at 20:45