[prev in list] [next in list] [prev in thread] [next in thread] 

List:       groovy-dev
Subject:    Re: [groovy-dev] Re: Nuts & Bolts of (dynamic) method execution
From:       Remi Forax <forax () univ-mlv ! fr>
Date:       2012-10-15 23:45:46
Message-ID: 507CA02A.4020402 () univ-mlv ! fr
[Download RAW message or body]

On 10/15/2012 11:23 PM, tnabe wrote:
>
>
> Rémi Forax wrote
>> On 10/14/2012 10:39 PM, Jochen Theodorou wrote:
>> Yes, seeing the benchmark will help.
>>
>> I suppose you use jdk7 (jdk8 has a brand new version of method handle
>> implementation)
>> and that you haven't store the MethodHandle in a static final field.
>> In that case, there is a limitation of the jdk7 method handle
>> implementation that
>> disable full inlining between the callsite and the called method so you
>> end up
>> with one function call. For j.l.r.Method, if you always call it from the
>> same callsite,
>> there is an optimization (the VM will deopt if you call it from another
>> callsite)
>> to avoid a bunch of checks that should be done, so you get full inlining
>> but
>> this optimization is usually not triggered in real code, only on
>> benchmark.
>>
>> So in one side, MethodHandle are not fully optimize and the other side,
>> your benchmark
>> enables full Method optimization.
>>
>> But, it's just an educated guess, I haven't seen your code :)
>>
>>
>> Rémi
> Well, a quite educated guess as it turns out.
> Yes, I'm using jdk 7 and no the MethodHandle reference is not in a static
> final, and yes the actual invoke is done at a single callsite (just the body
> of a for-loop)
> I've  posted the results
> <http://www.jroller.com/thnagy/entry/methodhandle_vs_reflection_benchmark>
> and the (ridiculously) simple code.
>
> I haven't yet written a test that would store the MethodHandle in a static
> final, but I will do so and post the results.
> I did have one question though about the JIT "inlining" the Method.invoke:
> what does that actually mean?  I suppose you mean that the JIT essentially
> elides the Method.invoke and what you're left with is a branch/jump
> instruction to the *actual* method?  Wow, that's pretty aggressive I
> wouldn't have guessed that the JIT could/would do that.

In fact the JIT is even more aggresive.

For this simple code:
public class ReflectMethodOptz {
   private static int COUNTER;

   public static void incr() {
     COUNTER++;
   }

   private static void foo(Method method) throws IllegalAccessException, 
InvocationTargetException {
     method.invoke(null, null);
   }

   public static void main(String[] args) throws Exception {
     Method method = ReflectMethodOptz.class.getMethod("incr");
     for(int i=0; i<100_000;i++) {
       foo(method);
     }
   }
}

the JIT first compile foo, as you can see the code of incr is cut&paste 
(inlined) into foo.

         88    4    b        ReflectMethodOptz::foo (8 bytes)
                             @ 3   java.lang.reflect.Method::invoke (63 
bytes)   inline (hot)
                               @ 15 
sun.reflect.Reflection::quickCheckMemberAccess (10 bytes)   inline (hot)
                                 @ 1 
sun.reflect.Reflection::getClassAccessFlags (0 bytes)   (intrinsic)
                                 @ 6 
java.lang.reflect.Modifier::isPublic (12 bytes)   inline (hot)
                               @ 22 
sun.reflect.Reflection::getCallerClass (0 bytes)   (intrinsic)
                               @ 37 
java.lang.reflect.AccessibleObject::checkAccess (96 bytes)   too big
                               @ 57 
sun.reflect.DelegatingMethodAccessorImpl::invoke (10 bytes)   inline (hot)
                !                @ 6 
sun.reflect.GeneratedMethodAccessor1::invoke (46 bytes)   inline (hot)
                                   @ 20   ReflectMethodOptz::incr (9 
bytes)   inline (hot)

then the JIT continue to optimize and replace the body of the loop in 
the main by a an assembly code (OSR) that
contains the code of foo, Method.invoke and incr twice to hoist (move 
things out of the loop) constants
if possible, but here there is nothing to hoist, the code is too simple.

        100    1 %  b        ReflectMethodOptz::main @ 17 (31 bytes)
                             @ 18   ReflectMethodOptz::foo (8 bytes)   
inline (hot)
                               @ 3   java.lang.reflect.Method::invoke 
(63 bytes)   inline (hot)
                                 @ 15 
sun.reflect.Reflection::quickCheckMemberAccess (10 bytes)   inline (hot)
                                   @ 1 
sun.reflect.Reflection::getClassAccessFlags (0 bytes)   (intrinsic)
                                   @ 6 
java.lang.reflect.Modifier::isPublic (12 bytes)   inline (hot)
                                 @ 22 
sun.reflect.Reflection::getCallerClass (0 bytes)   (intrinsic)
                                 @ 37 
java.lang.reflect.AccessibleObject::checkAccess (96 bytes)   too big
                                 @ 57 
sun.reflect.DelegatingMethodAccessorImpl::invoke (10 bytes)   inline (hot)
                !                  @ 6 
sun.reflect.GeneratedMethodAccessor1::invoke (46 bytes)   inline (hot)
                                     @ 20   ReflectMethodOptz::incr (9 
bytes)   inline (hot)
                             @ 18   ReflectMethodOptz::foo (8 bytes)   
inline (hot)
                               @ 3   java.lang.reflect.Method::invoke 
(63 bytes)   inline (hot)
                                 @ 15 
sun.reflect.Reflection::quickCheckMemberAccess (10 bytes)   inline (hot)
                                   @ 1 
sun.reflect.Reflection::getClassAccessFlags (0 bytes)   (intrinsic)
                                   @ 6 
java.lang.reflect.Modifier::isPublic (12 bytes)   inline (hot)
                                 @ 22 
sun.reflect.Reflection::getCallerClass (0 bytes)   (intrinsic)
                                 @ 37 
java.lang.reflect.AccessibleObject::checkAccess (96 bytes)   too big
                                 @ 57 
sun.reflect.DelegatingMethodAccessorImpl::invoke (10 bytes)   inline (hot)
                !                  @ 6 
sun.reflect.GeneratedMethodAccessor1::invoke (46 bytes)   inline (hot)
                                     @ 20   ReflectMethodOptz::incr (9 
bytes)   inline (hot)

so the generated code is like this:

   0x00007f4da4639630: mov    $0xeed0bdc0,%r10   ;   get 
ReflectMethodOptz.class in r10
   0x00007f4da463963a: incl   0x80(%r10)         ; increment field 0x80 
(COUNTER)
   0x00007f4da4639641: inc    %ebx               ; i++
   0x00007f4da4639643: test   %eax,0x8f849b7(%rip)        ; check if GC 
asks to stop current execution
   0x00007f4da4639649: cmp    $0x186a0,%ebx                 ; if i == 
100_000
   0x00007f4da463964f: jge    0x00007f4da4639715          ; jump to the 
end of the loop

plus a bunch of code that test that the method j.l.reflect.Method access 
don't change.

BTW you can see that for Hotspot a static field is stored as a classical 
field of the Class object
and also that the escape analysis is defeated here, the JIT has no idea 
that the local variable method
will never changed, it's a know side effect of the way Hotspot replaces 
loop body by assembly code.

Rémi


---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic