Estoy escribiendo un código en Java donde, en algún momento, el flujo del programa está determinado por si dos variables int, "a" y "b", no son cero (nota: a y b nunca son negativas, y nunca dentro del rango de desbordamiento de enteros).
Puedo evaluarlo con
if (a != 0 && b != 0) { /* Some code */ }
O alternativamente
if (a*b != 0) { /* Some code */ }
Como espero que ese código se ejecute millones de veces por ejecución, me preguntaba cuál sería más rápido. Hice el experimento comparándolos en una enorme matriz generada aleatoriamente, y también tenía curiosidad por ver cómo la escasez de la matriz (fracción de datos = 0) afectaría los resultados:
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);
}
Y los resultados muestran que si espera que "a" o "b" sea igual a 0 más del ~ 3% del tiempo, a*b != 0
es más rápido que a!=0 && b!=0
:
Tengo curiosidad por saber por qué. ¿Alguien podría arrojar algo de luz? ¿Es el compilador o está en el nivel de hardware?
Editar: Por curiosidad ... ahora que aprendí sobre la predicción de rama, me preguntaba qué mostraría la comparación analógica para a OR b no es cero:
Vemos el mismo efecto de predicción de ramificación que el esperado, curiosamente, el gráfico se voltea un poco a lo largo del eje X.
Actualizar
1- Agregué !(a==0 || b==0)
al análisis para ver qué pasa.
2- También incluí a != 0 || b != 0
, (a+b) != 0
y (a|b) != 0
por curiosidad, después de aprender sobre la predicción de ramas. Pero no son lógicamente equivalentes a las otras expresiones, porque solo a OR b no debe ser cero para devolver verdadero, por lo que no deben compararse para la eficiencia del procesamiento.
3- También agregué el punto de referencia real que usé para el análisis, que es solo iterar una variable int arbitraria.
4- Algunas personas sugirieron incluir a != 0 & b != 0
en lugar de a != 0 && b != 0
, con la predicción de que se comportaría más estrechamente a*b != 0
porque eliminaríamos el efecto de predicción de rama. No sabía que &
podría usarse con variables booleanas, pensé que solo se usaba para operaciones binarias con enteros.
Nota: En el contexto en el que estaba considerando todo esto, el desbordamiento int no es un problema, pero definitivamente es una consideración importante en contextos generales.
CPU: Intel Core i7-3610QM @ 2.3GHz
Versión de Java: 1.8.0_45
Java (TM) SE Runtime Environment (compilación 1.8.0_45-b14)
Java HotSpot (TM) VM de servidor de 64 bits (compilación 25.45-b02, modo mixto)
a != 0 & b != 0
.
a*b!=0
tiene una rama menos
(1<<16) * (1<<16) == 0
Sin embargo, ambos son diferentes de cero.
a*b
es cero si uno de a
y b
es cero; a|b
es cero solo si ambos lo son.
if (!(a == 0 || b == 0))
? Los microbenchmarks son notoriamente poco confiables, es poco probable que sea realmente medible (~ 3% me parece un margen de error).