Warum gibt Inlining von Math.max über 200x langsameren Code?

8

Ich habe vor kurzem begonnen, Java-Code zu vergleichen, um die besten Ergebnisse für mein Programm zu erzielen, und habe etwas Merkwürdiges bemerkt. Ich habe nämlich die folgenden Methoden getestet:

%Vor%

und bekam diese Ergebnisse:

%Vor%

Nach der Suche über SO (zB Is Math.max ( a, b) oder (a & gt; b)? a: b schneller in Java? ) es war sicher für mich, dass test1 nicht so viel langsamer sein sollte.

Diese Methoden wurden zufällig in 8 Threads in 30 Sekunden getestet und jeder von mir ausgeführte Benchmark scheint ähnlich zu sein. Ich benutze jdk1.8.0_45 .

Warum ist also test1 mehr als 200 Mal langsamer als test0 ?

    
Kamil Jarosz 19.07.2016, 19:34
quelle

4 Antworten

3

Da Math.max eine statische Funktion ist, kann der Compiler herausfinden, dass der Code gar nichts tut und einfach die Ausführung optimiert, indem er nicht ausgeführt wird!

Die Variable m ist lokal für die Funktion, das Zuweisen ist nicht hilfreich, da sie nie gelesen wird.

Sie müssen sicherstellen, dass die Ausführung etwas ändert, so dass es vom Compiler nicht aggressiv optimiert wird.

Sie können beispielsweise einfach den Wert von m am Ende des Tests drucken oder m zu einer Klassenvariablen machen, auf die später zugegriffen werden kann, oder sogar das Ergebnis wie ursprünglich im Kommentar vorgeschlagen zusammenfassen.

    
Jean Logeart 19.07.2016, 19:59
quelle
2

Math.max(a,b) kann sehr aggressiv / offensichtlich in eine native Anweisung für den Prozessor optimiert werden.

Für das ternäre System wäre die einfache Umwandlung in einen Prozessorbefehl ein Vergleich + Sprung, besonders der Sprung ist teuer.

Um das ternäre in das JIT (Just-in-time-Compiler) zu optimieren, muss man erkennen, dass der Code ein Maximum ausdrückt und der native Befehl am besten ist.

Das JIT könnte dies eventuell erkennen, aber bis zu diesem Punkt wird es langsamer sein.

    
k5_ 19.07.2016 19:54
quelle
1

Nur um (in gewissem Maße) zu bestätigen, was Jean Logeart in seiner Antwort gesagt hat: Beim Hinzufügen eines trivialen main das ruft beide Methoden auf, wie in

%Vor%

Und es in einem Hotspot Disassembler VM mit

ausführen %Vor%

dann (auf Win7 / 64 mit Java 1.8.0_92) wird der letzte Maschinencode der Methode test0 sein

%Vor%

Ja, es tut im Grunde nichts.

Überraschenderweise macht das JIT offensichtlich für test1 eine ungerade Schleifenaufrollung, aber nicht scheint zu erkennen, dass die Methode nutzlos und nebenwirkungsfrei ist (es könnte weg optimiert werden, aber es ist nicht

Die resultierende Baugruppe ist ziemlich groß ....:

%Vor%

Ich frage mich, warum die Methode nicht optimiert ist. Vielleicht ist es noch einmal wert, als separate Frage gefragt zu werden. Aber mein Bauchgefühl ist, dass es ein Nebeneffekt der künstlichen Testkonfiguration ist, die das letzte n und die Verwendung der Schleifenvariable für den inneren Ausdruck beinhaltet. In ähnlichen (realistischeren) Aufbauten werden solche nutzlosen, nebenwirkungsfreien Methoden meist ziemlich zuverlässig eliminiert.

    
Marco13 20.07.2016 11:36
quelle
0

Wenn eine Methode zum ersten Mal ausgeführt wird, hatte die JVM nicht die Möglichkeit, die Methode mithilfe ihres Just-in-Time-Compilers erneut zu kompilieren. Dies ist das Problem mit so vielen Micro-Benchmark-Bemühungen. Angenommen, main () ruft test1 () auf, main () ruft test1 () mehrmals auf und misst die Zeit jedes Aufrufs von test1 (). Sie werden sehen, dass nachfolgende Aufrufe von test1 () viel schneller ausführen!

%Vor%     
Mike 19.07.2016 22:14
quelle

Tags und Links