Estou escrevendo algum código em Java, onde, em algum momento, o fluxo do programa é determinado por duas variáveis int, "a" e "b", serem diferentes de zero (nota: aeb nunca são negativas e nunca dentro do intervalo de estouro inteiro).
Eu posso avaliar isso com
if (a != 0 && b != 0) { /* Some code */ }
Ou alternativamente
if (a*b != 0) { /* Some code */ }
Como eu esperava que esse trecho de código fosse executado milhões de vezes por execução, fiquei pensando qual seria o mais rápido. Fiz o experimento comparando-os em uma enorme matriz gerada aleatoriamente e também fiquei curioso para ver como a escarsidade da matriz (fração de dados = 0) afetaria os 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);
}
E os resultados mostram que se você espera que "a" ou "b" seja igual a 0 mais que ~ 3% do tempo, a*b != 0
é mais rápido que a!=0 && b!=0
:
Estou curioso para saber o porquê. Alguém poderia lançar alguma luz? É o compilador ou está no nível do hardware?
Edit: Por curiosidade ... agora que aprendi sobre a previsão de ramificação, fiquei pensando o que a comparação analógica mostraria para um OR b é diferente de zero:
Vemos o mesmo efeito de previsão de ramificação como esperado, curiosamente, o gráfico é um pouco invertido ao longo do eixo X.
Atualizar
1- Eu adicionei !(a==0 || b==0)
à análise para ver o que acontece.
2- Incluí também a != 0 || b != 0
, (a+b) != 0
e (a|b) != 0
por curiosidade, depois de aprender sobre a previsão de ramificações. Mas eles não são logicamente equivalentes às outras expressões, porque apenas um OR b precisa ser diferente de zero para retornar true, portanto, eles não devem ser comparados para obter eficiência de processamento.
3- Também adicionei o benchmark real que usei para a análise, que está apenas repetindo uma variável int arbitrária.
4- Algumas pessoas sugeriam incluir a != 0 & b != 0
, ao contrário a != 0 && b != 0
, com a previsão de que ele se comportaria mais de perto a*b != 0
porque removeríamos o efeito de previsão do ramo. Eu não sabia que &
poderia ser usado com variáveis booleanas, pensei que era usado apenas para operações binárias com números inteiros.
Nota: No contexto em que eu estava considerando tudo isso, o excesso de int não é um problema, mas essa é definitivamente uma consideração importante em contextos gerais.
CPU: Intel Core i7-3610QM a 2.3GHz
Versão Java: 1.8.0_45
Java (TM) SE Runtime Environment (build 1.8.0_45-b14)
VM do servidor Java HotSpot (TM) de 64 bits (build 25.45-b02, modo misto)
a != 0 & b != 0
.
a*b!=0
tem menos um ramo
(1<<16) * (1<<16) == 0
no entanto, ambos são diferentes de zero.
a*b
é zero se um de a
e b
é zero; a|b
é zero apenas se ambos forem.
if (!(a == 0 || b == 0))
? Microbenchmarks são notoriamente não confiáveis, é improvável que seja realmente mensurável (~ 3% parece uma margem de erro para mim).