Ich schreibe einen Code in Java, bei dem der Programmfluss irgendwann dadurch bestimmt wird, ob zwei int-Variablen "a" und "b" ungleich Null sind (Anmerkung: a und b sind niemals negativ, und niemals innerhalb eines ganzzahligen Überlaufbereichs).
Ich kann es mit bewerten
if (a != 0 && b != 0) { /* Some code */ }
Oder alternativ
if (a*b != 0) { /* Some code */ }
Da ich davon ausgehe, dass dieser Code pro Lauf millionenfach ausgeführt wird, habe ich mich gefragt, welcher Code schneller sein würde. Ich habe das Experiment durchgeführt, indem ich sie auf einem riesigen zufällig generierten Array verglichen habe, und ich war auch gespannt, wie sich die Sparsity des Arrays (Bruchteil der Daten = 0) auf die Ergebnisse auswirken würde:
long time;
final int len = 50000000;
int arbitrary = 0;
int[][] nums = new int[2][len];
for (double fraction = 0 ; fraction <= 0.9 ; fraction += 0.0078125) {
for(int i = 0 ; i < 2 ; i++) {
for(int j = 0 ; j < len ; j++) {
double random = Math.random();
if(random < fraction) nums[i][j] = 0;
else nums[i][j] = (int) (random*15 + 1);
}
}
time = System.currentTimeMillis();
for(int i = 0 ; i < len ; i++) {
if( /*insert nums[0][i]*nums[1][i]!=0 or nums[0][i]!=0 && nums[1][i]!=0*/ ) arbitrary++;
}
System.out.println(System.currentTimeMillis() - time);
}
Und die Ergebnisse zeigen, dass, wenn Sie erwarten, dass "a" oder "b" in mehr als ~ 3% der Fälle gleich 0 a*b != 0
ist, dies schneller ist als a!=0 && b!=0
:
Ich bin gespannt warum. Könnte jemand etwas Licht ins Dunkel bringen? Ist es der Compiler oder auf Hardwareebene?
Bearbeiten: Aus Neugier ... jetzt, wo ich etwas über die Verzweigungsvorhersage gelernt habe, habe ich mich gefragt, was der analoge Vergleich für einen OR b ungleich Null zeigen würde:
Wir sehen den gleichen Effekt der Verzweigungsvorhersage wie erwartet, interessanterweise ist der Graph entlang der X-Achse etwas gespiegelt.
Aktualisieren
1- Ich !(a==0 || b==0)
habe der Analyse hinzugefügt, um zu sehen, was passiert.
2- ich ebenfalls enthalten a != 0 || b != 0
, (a+b) != 0
und (a|b) != 0
aus Neugier, nach etwa Verzweigungsvorhersage zu lernen. Sie sind jedoch nicht logisch äquivalent zu den anderen Ausdrücken, da nur ein ODER b ungleich Null sein muss, um true zurückzugeben, sodass sie nicht für die Verarbeitungseffizienz verglichen werden sollen.
3- Ich habe auch den tatsächlichen Benchmark hinzugefügt, den ich für die Analyse verwendet habe, bei dem nur eine beliebige int-Variable iteriert wird.
4- Einige Leute schlugen vor, a != 0 & b != 0
im Gegensatz zu einzubeziehen a != 0 && b != 0
, mit der Vorhersage, dass es sich enger verhalten würde, a*b != 0
weil wir den Verzweigungsvorhersageeffekt entfernen würden. Ich wusste nicht, dass &
dies mit booleschen Variablen verwendet werden kann. Ich dachte, es wird nur für binäre Operationen mit ganzen Zahlen verwendet.
Hinweis: In dem Kontext, in dem ich all dies in Betracht gezogen habe, ist int overflow kein Problem, aber das ist definitiv eine wichtige Überlegung in allgemeinen Kontexten.
CPU: Intel Core i7-3610QM bei 2,3 GHz
Java-Version: 1.8.0_45
Java (TM) SE-Laufzeitumgebung (Build 1.8.0_45-b14)
Java HotSpot (TM) 64-Bit-Server-VM (Build 25.45-b02, gemischter Modus)
a != 0 & b != 0
.
a*b!=0
hat einen Zweig weniger
(1<<16) * (1<<16) == 0
dennoch unterscheiden sich beide von Null.
a*b
wenn der Überlauf ignoriert wird, ist er Null, wenn einer von a
und b
Null ist. a|b
ist nur dann Null, wenn beide sind.
if (!(a == 0 || b == 0))
? Mikrobenchmarks sind notorisch unzuverlässig, es ist unwahrscheinlich, dass dies wirklich messbar ist (~ 3% klingt für mich nach einer Fehlerquote).