Sto scrivendo del codice in Java dove, ad un certo punto, il flusso del programma è determinato dal fatto che due variabili int, "a" e "b", siano diverse da zero (nota: aeb non sono mai negative, e mai all'interno dell'intervallo di overflow dei numeri interi).
Posso valutarlo con
if (a != 0 && b != 0) { /* Some code */ }
O in alternativa
if (a*b != 0) { /* Some code */ }
Poiché mi aspetto che quel pezzo di codice venga eseguito milioni di volte per esecuzione, mi chiedevo quale sarebbe stato più veloce. Ho fatto l'esperimento confrontandoli su un enorme array generato casualmente ed ero anche curioso di vedere come la scarsità dell'array (frazione di dati = 0) avrebbe influenzato i risultati:
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);
}
E i risultati mostrano che se ti aspetti che "a" o "b" sia uguale a 0 in più di ~ 3% del tempo, a*b != 0
è più veloce di a!=0 && b!=0
:
Sono curioso di sapere perché. Qualcuno potrebbe far luce? È il compilatore o è a livello hardware?
Modifica: per curiosità ... ora che ho imparato a conoscere la previsione del ramo, mi chiedevo che cosa il confronto analogico avrebbe mostrato per un OR b è diverso da zero:
Vediamo lo stesso effetto della previsione del ramo come previsto, il grafico è in qualche modo capovolto lungo l'asse X.
Aggiornare
1- Ho aggiunto !(a==0 || b==0)
all'analisi per vedere cosa succede.
2- Ho anche incluso a != 0 || b != 0
, (a+b) != 0
e (a|b) != 0
per curiosità, dopo aver appreso la previsione del ramo. Ma non sono logicamente equivalenti alle altre espressioni, perché solo un OR b deve essere diverso da zero per restituire vero, quindi non devono essere confrontati per l'efficienza di elaborazione.
3- Ho anche aggiunto il benchmark effettivo che ho usato per l'analisi, che sta solo ripetendo una variabile int arbitraria.
4- Alcune persone hanno suggerito di includere a != 0 & b != 0
invece di a != 0 && b != 0
, con la previsione che si sarebbe comportato più da vicino a*b != 0
perché avremmo rimosso l'effetto di previsione del ramo. Non sapevo che &
potesse essere usato con variabili booleane, pensavo che fosse usato solo per operazioni binarie con numeri interi.
Nota: nel contesto che stavo considerando tutto ciò, int overflow non è un problema, ma è sicuramente una considerazione importante in contesti generali.
CPU: Intel Core i7-3610QM @ 2.3GHz
Versione Java: 1.8.0_45
Java (TM) SE Runtime Environment (build 1.8.0_45-b14)
Java HotSpot (TM) VM a 64 bit Server (build 25.45-b02, modalità mista)
a != 0 & b != 0
.
a*b!=0
ha una filiale in meno
(1<<16) * (1<<16) == 0
eppure entrambi sono diversi da zero.
a*b
è zero se uno di a
ed b
è zero; a|b
è zero solo se entrambi lo sono.
if (!(a == 0 || b == 0))
? I microbench sono notoriamente inaffidabili, è improbabile che ciò sia realmente misurabile (~ 3% mi sembra un margine di errore).