Zależy również od zestawu instrukcji. Twój procesor będzie miał w każdej chwili kilka jednostek obliczeniowych, a uzyskasz maksymalną przepustowość, jeśli wszystkie będą cały czas wypełnione. Zatem wykonywanie pętli mul jest równie szybkie jak wykonywanie pętli lub dodaje - ale to samo nie obowiązuje, jeśli wyrażenie staje się bardziej złożone.
na przykład wykorzystać tę pętlę:
for(int j=0;j<NUMITER;j++) {
for(int i=1;i<NUMEL;i++) {
bla += 2.1 + arr1[i] + arr2[i] + arr3[i] + arr4[i] ;
}
}
dla NUMITER = 10^7 NUMEL = 10^2, obydwie macierze zainicjowana do niewielkiej liczby dodatnie (Nan jest znacznie mniejsza), odbywa 6,0 sekundy przy użyciu wskaźnika podwaja się na 64-bitowym proc. Gdybym zastąpić pętlę z
bla += 2.1 * arr1[i] + arr2[i] + arr3[i] * arr4[i] ;
To trwa tylko 1,7 sekundy ... więc skoro my „przesadził” dodatki, na muls były zasadniczo wolny; a pomniejszenie dodatków pomogło. To get bardziej skomplikowane:
bla += 2.1 + arr1[i] * arr2[i] + arr3[i] * arr4[i] ;
- sama mul/dodaj dystrybucję, ale teraz stała dodaje się zamiast mnoży się - trwa 3,7 sekundy. Twój procesor jest prawdopodobnie zoptymalizowany do wydajniejszego wykonywania typowych obliczeń numerycznych; więc iloczyn iloczynu ilości mulów i skalowanych sum jest tak dobry, jak to tylko możliwe; dodawanie stałych nie jest tak powszechne, więc jest wolniej ...
bla += someval + arr1[i] * arr2[i] + arr3[i] * arr4[i] ; /*someval == 2.1*/
ponownie zajmuje 1,7 sekundy.
bla += someval + arr1[i] + arr2[i] + arr3[i] + arr4[i] ; /*someval == 2.1*/
(taki sam jak pętla początkowa, ale bez drogiego stałego dodawania: 2.1 sekundy)
bla += someval * arr1[i] * arr2[i] * arr3[i] * arr4[i] ; /*someval == 2.1*/
(głównie muls, ale dodatkowo: 1,9 sek)
więc w zasadzie; Trudno powiedzieć, które z nich jest szybsze, ale jeśli chcesz uniknąć wąskich gardeł, ważniejsze jest posiadanie rozsądnej mieszanki, unikanie NaN lub INF, unikanie dodawania stałych. Niezależnie od tego, co robisz, upewnij się, że testujesz i testujesz różne ustawienia kompilatora, ponieważ często małe zmiany mogą po prostu sprawić różnicę.
Niektóre więcej przypadków:
bla *= someval; // someval very near 1.0; takes 2.1 seconds
bla *= arr1[i] ;// arr1[i] all very near 1.0; takes 66(!) seconds
bla += someval + arr1[i] * arr2[i] + arr3[i] * arr4[i] ; // 1.6 seconds
bla += someval + arr1[i] * arr2[i] + arr3[i] * arr4[i] ; //32-bit mode, 2.2 seconds
bla += someval + arr1[i] * arr2[i] + arr3[i] * arr4[i] ; //32-bit mode, floats 2.2 seconds
bla += someval * arr1[i]* arr2[i];// 0.9 in x64, 1.6 in x86
bla += someval * arr1[i];// 0.55 in x64, 0.8 in x86
bla += arr1[i] * arr2[i];// 0.8 in x64, 0.8 in x86, 0.95 in CLR+x64, 0.8 in CLR+x86
Zestaw instrukcji to dobry punkt, mam ludzi, z którymi pracuję, którzy nalegają, aby 200-punktowy procesor DSP wykonał 600 stałych punktów DSP. Nie wykonują absolutnie żadnych skomplikowanych operacji na pętli i spędzają więcej czasu na przetwarzaniu I/O niż na obliczeniach. Szybszy stały procesor punktowy wygrywa w oparciu o ogólną kombinację instrukcji, ale ludzie sądzą, że jednostki FP są magią, a nie HW implementacją struktury danych. – NoMoreZealots
Ach tak, magiczna aplikacja ;-) - to niefortunne. –
ładne wyjaśnienie dzięki intuicyjnym przykładom! –