2013-03-25 10 views
19

Ich schrieb einen kleinen Benchmark, der die Leistung von java.lang.invoke.MethodHandle, java.lang.reflect.Method und direkte Aufrufe von Methoden testet.MethodHandle Leistung

Ich lese, dass MethodHandle.invoke() Leistung fast das gleiche wie Direktanrufe. Aber meine Testergebnisse zeigen eine andere: MethodHandle rufen etwa dreimal langsamer als die Reflexion auf. Was ist mein Problem? Kann dies auf einige JIT-Optimierungen zurückzuführen sein?

public class Main { 
    public static final int COUNT = 100000000; 
    static TestInstance test = new TestInstance(); 

    static void testInvokeDynamic() throws NoSuchMethodException, IllegalAccessException { 
     int [] ar = new int[COUNT]; 

     MethodHandles.Lookup lookup = MethodHandles.lookup(); 
     MethodType mt = MethodType.methodType(int.class); 

     MethodHandle handle = lookup.findStatic(TestInstance.class, "publicStaticMethod", mt) ; 

     try { 
      long start = System.currentTimeMillis(); 

      for (int i=0; i<COUNT; i++) { 
       ar[i] = (int)handle.invokeExact(); 
      } 

      long stop = System.currentTimeMillis(); 

      System.out.println(ar); 

      System.out.println("InvokeDynamic time: " + (stop - start)); 
     } catch (Throwable throwable) { 
      throwable.printStackTrace(); 
     } 
    } 

    static void testDirect() { 
     int [] ar = new int[COUNT]; 

     try { 
      long start = System.currentTimeMillis(); 

      for (int i=0; i<COUNT; i++) { 
       ar[i] = TestInstance.publicStaticMethod(); 
      } 

      long stop = System.currentTimeMillis(); 

      System.out.println(ar); 

      System.out.println("Direct call time: " + (stop - start)); 
     } catch (Throwable throwable) { 
      throwable.printStackTrace(); 
     } 
    } 

    static void testReflection() throws NoSuchMethodException { 
     int [] ar = new int[COUNT]; 

     Method method = test.getClass().getMethod("publicStaticMethod"); 

     try { 
      long start = System.currentTimeMillis(); 

      for (int i=0; i<COUNT; i++) { 
       ar[i] = (int)method.invoke(test); 
      } 

      long stop = System.currentTimeMillis(); 

      System.out.println(ar); 

      System.out.println("Reflection time: " + (stop - start)); 
     } catch (Throwable throwable) { 
      throwable.printStackTrace(); 
     } 
    } 

    static void testReflectionAccessible() throws NoSuchMethodException { 
     int [] ar = new int[COUNT]; 

     Method method = test.getClass().getMethod("publicStaticMethod"); 
     method.setAccessible(true); 

     try { 
      long start = System.currentTimeMillis(); 

      for (int i=0; i<COUNT; i++) { 
       ar[i] = (int)method.invoke(test); 
      } 

      long stop = System.currentTimeMillis(); 

      System.out.println(ar); 

      System.out.println("Reflection accessible time: " + (stop - start)); 
     } catch (Throwable throwable) { 
      throwable.printStackTrace(); 
     } 
    } 

    public static void main(String ... args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InterruptedException { 
     Thread.sleep(5000); 

     Main.testDirect(); 
     Main.testInvokeDynamic(); 
     Main.testReflection(); 
     Main.testReflectionAccessible(); 

     System.out.println("\n___\n"); 

     System.gc(); 
     System.gc(); 

     Main.testDirect(); 
     Main.testInvokeDynamic(); 
     Main.testReflection(); 
     Main.testReflectionAccessible(); 
    } 
} 

Umwelt: java version "1.7.0_11" Java (TM) SE Runtime Environment (build 1.7.0_11-b21) Java HotSpot (TM) 64-Bit Server VM (Build 23.6-b04, mixed Modus) OS - Windows-7 64

+1

zuerst sicherstellen, dass Sie wissen, wie Benchmarks zu schreiben, finden Sie unter: http://www.ibm.com/developerworks/java/library/j-jtp02225/index.html –

+0

@NathanHughes Was mit seiner Benchmark falsch? – Andremoniy

+0

@Andremoniy: das offensichtlichste Problem ist, es gibt keine Aufwärmung der JVM. –

Antwort

1

Es erscheint andere haben gesehen, ähnliche Ergebnisse: http://vanillajava.blogspot.com/2011/08/methodhandle-performance-in-java-7.html

Hier ist jemand anderes: http://andrewtill.blogspot.com/2011/08/using-method-handles.html

ich lief, dass der zweite und sah, dass sie etwa die gleiche Geschwindigkeit hatten und sogar diesen Test aufwärmen konnten. Ich habe es jedoch so korrigiert, dass es nicht jedes Mal ein args-Array erstellt hat. Bei der Standardzählung führte dies zum gleichen Ergebnis: Die Methodenhandles waren etwas schneller. Aber ich habe eine Zählung von 10000000 (Standard * 10) und Reflexion ging viel schneller.

Also, ich würde empfehlen, mit Parametern zu testen. Ich frage mich, ob MethodHandles effizienter mit Parametern umgehen? Prüfen Sie auch, wie Sie die Anzahl ändern - wie viele Iterationen.

@ meriton Kommentar auf die Frage Links zu seiner Arbeit und sieht sehr hilfreich: Calling a getter in Java though reflection: What's the fastest way to repeatedly call it (performance and scalability wise)?

0

Wenn die publicStaticMethod eine einfache Implementierung war wie eine konstante Rückkehr Es ist sehr viel möglich, dass die direkte Aufruf war in ausgekleideten von JIT Compiler. Dies ist möglicherweise mit methodHandles nicht möglich.

RE http://vanillajava.blogspot.com/2011/08/methodhandle-performance-in-java-7.html Beispiel, wie erwähnt, dass Kommentare nicht sehr gut umgesetzt werden. Wenn Sie in der Berechnungsschleife den Typ casting in int (statt Integer) ändern, sind die Ergebnisse näher am direkten Methodenaufruf.

Mit der verwickelten Implementierung von (Erstellen und Aufrufen einer zukünftigen Aufgabe, die ein zufälliges int zurückgibt) gab Benchmark mit näheren Zahlen, wo MethodStatic maximal ~ 10% langsamer als direkte Methode war. So sehen Sie möglicherweise 3-mal langsamere Leistung aufgrund von JIT-Optimierungen

2

Sieht aus wie dies indirekt von @AlekseyShipilev in Bezug auf eine andere Abfrage beantwortet wurde. In der folgenden Verbindung How can I improve performance of Field.set (perhap using MethodHandles)?

Wenn Sie durchlesen, werden Sie zusätzliche Benchmarks sehen, die ähnliche Ergebnisse zeigen. Es ist wahrscheinlich, dass die direkten Anrufe einfach durch JIT in einer Weise optimiert werden, die über die Feststellungen Laut, der Unterschied ist: MethodHandle.invoke = ~ 195ns MethodHandle.invokeExact = ~ 10ns Direkte Anrufe = 1.266ns

Also - direkte Anrufe werden immer noch schneller sein, aber MH ist sehr schnell. Für die meisten Anwendungsfälle sollte dies ausreichend sein und ist sicherlich schneller als das alte Reflektionsrahmenwerk (übrigens - nach den obigen Ergebnissen ist die Reflektion unter java8 vm ebenfalls deutlich schneller)

Wenn dieser Unterschied in Ihrem System signifikant ist, Ich würde vorschlagen, andere Muster als direkte Reflexion zu finden, die direkte Anrufe unterstützen wird.

-1

Führen Sie es mit VM-Optionen: -Djava.compiler = NONE, was verbotenes JIT bedeutet. Dann werden Sie den Unterschied sehen.