Wie kann Java ineffizienten Code schneller ausführen als effizienter Code?

9

Im folgenden Codefragment ist Foo1 eine Klasse, die jedes Mal, wenn die Methode bar() aufgerufen wird, einen Zähler erhöht. Foo2 macht dasselbe, aber mit einer zusätzlichen indirekten Ebene.

Ich würde erwarten, dass Foo1 schneller ist als Foo2 , aber in der Praxis ist Foo2 konsistent um 40% schneller als Foo1 . Wie optimiert die JVM den Code so, dass Foo2 schneller läuft als Foo1 ?

Einige Details

  • Der Test wurde mit java -server CompositionTest .
  • ausgeführt
  • Die Ausführung des Tests mit java -client CompositionTest führt zu den erwarteten Ergebnissen, dass Foo2 langsamer ist als Foo1 .
  • Die Reihenfolge der Schleifen zu ändern macht keinen Unterschied.
  • Die Ergebnisse wurden mit java6 auf beiden JVMs von sun und openjdk verifiziert.

Der Code

%Vor%     
TianyuZhu 17.05.2012, 23:39
quelle

2 Antworten

1

Der Versuch, die Leistung moderner Sprachen vorherzusagen, ist nicht sehr produktiv.

Die JVM wird ständig modifiziert, um die Leistung gängiger, lesbarer Strukturen zu erhöhen, was im Gegensatz dazu ungewöhnlichen, umständlichen Code langsamer macht.

Schreiben Sie Ihren Code so klar wie möglich - wenn Sie dann wirklich einen Punkt identifizieren, an dem Ihr Code tatsächlich als zu langsam zum Übergeben von schriftlichen Spezifikationen identifiziert wird, müssen Sie möglicherweise einige Bereiche manuell anpassen - aber das wird wahrscheinlich beinhalten große, einfache Ideen wie Objekt-Caches, JVM-Optionen optimieren und wirklich dummen / falschen Code beseitigen (Falsche Datenstrukturen können RIESIG sein, ich habe einmal eine ArrayList in eine LinkedList geändert und eine Operation von 10 Minuten auf 5 Sekunden reduziert, Multi-Threading a Ping-Operation, die ein Klasse-B-Netzwerk entdeckte, nahm eine Operation von 8 Stunden zu Minuten).

    
Bill K 18.05.2012, 15:21
quelle
2

Ihre Microbench-Markierung ist bedeutungslos. Auf meinem Computer läuft der Code in ungefähr 8ms für jede Schleife ... Um eine sinnvolle Zahl zu haben, sollte ein Benchmark wahrscheinlich für mindestens eine Sekunde laufen.

Wenn beide für etwa eine Sekunde ausgeführt werden (Hinweis, Sie brauchen mehr als Integer.MAX_VALUE Wiederholungen) Ich finde, dass die Laufzeiten von beiden identisch sind.

Die wahrscheinlichste Erklärung dafür ist, dass der JIT-Compiler bemerkt hat, dass Ihre Indirektion sinnlos ist und sie (oder zumindest die Methodenaufrufe) so optimiert wurde, dass der in beiden Schleifen ausgeführte Code identisch ist.

Er kann das, weil er weiß, dass bar in Foo2 effektiv final ist, und dass das Argument für den Foo2 -Konstruktor immer ein Foo1 ist (zumindest in unserem kleinen Test) . Auf diese Weise kennt es den genauen Code-Pfad, wenn Foo2.bar aufgerufen wird. Es weiß auch, dass diese Schleife viele Male ausgeführt wird (in der Tat weiß sie genau, wie oft die Schleife ausgeführt wird) - so scheint es eine gute Idee zu sein, den Code einzubinden.

Ich habe keine Ahnung, ob das genau das ist, aber das sind alles logische Beobachtungen, die ich mit dem JIT über den Code machen konnte. Vielleicht könnten in Zukunft einige JIT-Compiler sogar die gesamte while-Schleife optimieren und einfach die Anzahl auf Wiederholungen setzen, aber das erscheint ziemlich unwahrscheinlich.

    
Dunes 18.05.2012 16:22
quelle