Piszę trochę kodu w Javie, w którym w pewnym momencie przepływ programu zależy od tego, czy dwie zmienne int, „a” i „b”, są niezerowe (uwaga: a i b nigdy nie są ujemne, a nigdy w zakresie przepełnienia liczb całkowitych).
Mogę to ocenić za pomocą
if (a != 0 && b != 0) { /* Some code */ }
Lub alternatywnie
if (a*b != 0) { /* Some code */ }
Ponieważ oczekuję, że ten fragment kodu będzie uruchamiany miliony razy na uruchomienie, zastanawiałem się, który z nich będzie szybszy. Zrobiłem eksperyment, porównując je na ogromnej losowo wygenerowanej tablicy, i byłem również ciekawy, jak rzadkość tablicy (część danych = 0) wpłynie na wyniki:
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);
}
A wyniki pokazują, że jeśli spodziewasz się, że „a” lub „b” będzie równe 0 przez więcej niż ~ 3% czasu, a*b != 0
jest to szybsze niż a!=0 && b!=0
:
Jestem ciekawy, dlaczego. Czy ktoś mógłby rzucić trochę światła? Czy to jest kompilator, czy jest na poziomie sprzętowym?
Edycja: Z ciekawości ... teraz, gdy dowiedziałem się o przewidywaniu gałęzi, zastanawiałem się, co pokaże porównanie analogowe dla OR b jest niezerowe:
Widzimy taki sam efekt przewidywania gałęzi, jak oczekiwano, co ciekawe, wykres jest nieco odwrócony wzdłuż osi X.
Aktualizacja
1- Dodałem !(a==0 || b==0)
do analizy, aby zobaczyć, co się stanie.
2- ja również a != 0 || b != 0
, (a+b) != 0
i (a|b) != 0
z ciekawości, gdy dowiedział się o przewidywania rozgałęzień. Ale nie są one logicznie równoważne z innymi wyrażeniami, ponieważ tylko OR b musi być niezerowe, aby zwrócić true, więc nie należy ich porównywać pod kątem wydajności przetwarzania.
3- Dodałem również rzeczywisty test porównawczy, którego użyłem do analizy, która jest po prostu iteracją dowolnej zmiennej int.
4 - Niektóre osoby sugerowały włączenie a != 0 & b != 0
w przeciwieństwie do a != 0 && b != 0
prognozy, że będzie się ona zachowywać ściślej, a*b != 0
ponieważ usuniemy efekt przewidywania gałęzi. Nie wiedziałem, że &
można go używać ze zmiennymi boolowskimi, myślałem, że był on używany tylko do operacji binarnych z liczbami całkowitymi.
Uwaga: W kontekście, który rozważałem, przepełnienie int nie jest problemem, ale jest to zdecydowanie ważna uwaga w kontekście ogólnym.
Procesor: Intel Core i7-3610QM @ 2,3 GHz
Wersja Java: 1.8.0_45
Środowisko wykonawcze Java (TM) SE (kompilacja 1.8.0_45-b14
) 64-bitowa maszyna wirtualna serwera Java HotSpot (TM) (kompilacja 25.45-b02, tryb mieszany)
a != 0 & b != 0
.
a*b!=0
ma jeden oddział mniej
(1<<16) * (1<<16) == 0
ale oba różnią się od zera.
a*b
wynosi zero, jeżeli jeden z a
a b
wynosi zero; a|b
wynosi zero tylko wtedy, gdy oba są.
if (!(a == 0 || b == 0))
? Znaki mikrodruku są notorycznie niewiarygodne, jest mało prawdopodobne, aby było to naprawdę wymierne (dla mnie ok. 3% brzmi jak margines błędu).