2010-05-20 25 views
15

Obecnie piszę trochę GLSL jak klas wektor matematyki w C++, a ja po prostu wdrożył abs() funkcję tak:Czym różni się o C++ abs math.h() w porównaniu do mojego abs()

template<class T> 
static inline T abs(T _a) 
{ 
    return _a < 0 ? -_a : _a; 
} 

porównałem jego prędkość do domyślnej C++ abs od math.h tak:

clock_t begin = clock(); 
for(int i=0; i<10000000; ++i) 
{ 
    float a = abs(-1.25); 
}; 

clock_t end = clock(); 
unsigned long time1 = (unsigned long)((float)(end-begin)/((float)CLOCKS_PER_SEC/1000.0)); 

begin = clock(); 
for(int i=0; i<10000000; ++i) 
{ 
    float a = myMath::abs(-1.25); 
}; 
end = clock(); 
unsigned long time2 = (unsigned long)((float)(end-begin)/((float)CLOCKS_PER_SEC/1000.0)); 

std::cout<<time1<<std::endl; 
std::cout<<time2<<std::endl; 

teraz domyślny abs trwa około 25ms natomiast kopalnia zajmuje 60. Chyba jest jakaś optymalizacja niski poziom dzieje. Czy ktoś wie, jak działa wewnętrznie? Różnica w wydajności nie jest niczym dramatycznym, ale jestem po prostu ciekawa!

+3

Być może mógłbyś pokazać źródło zespołu wygenerowane przez kompilator dla dwóch pętli i porównać to. W przeciwnym razie po prostu zgadujemy. –

+2

'abs' pobiera liczbę całkowitą i zwraca liczbę całkowitą. Używanie "abs" jest po prostu niewłaściwe w porównaniu. Zamiast tego użyj 'fabs' z' '. – kennytm

+0

Proszę, powiedz nam, jaki jest twój kompilator/środowisko. – jpalecek

Odpowiedz

1

Prawdopodobnie wersja biblioteki abs jest wewnętrzną funkcją, której zachowanie jest dokładnie znane kompilatorowi, który może nawet obliczyć wartość w czasie kompilacji (ponieważ w twoim przypadku jest znana) i zoptymalizować wywołanie. Powinieneś wypróbować swój benchmark z wartością znaną tylko w czasie wykonywania (dostarczonym przez użytkownika lub otrzymanym z rand() przed dwoma cyklami).

Jeśli nadal występuje różnica, może to wynikać z tego, że abs biblioteki jest zapisywany bezpośrednio w ręcznie kutym zespole za pomocą magicznych sztuczek, więc może być trochę szybszy niż wygenerowany.

4

Prawdopodobnie właśnie wykorzystuje maskę bitową aby ustawić bit znaku 0.

+0

hej, nie mam dużego doświadczenia programowanie bitowe, jak by to działało? – moka

+0

@moka: Patrz http://en.wikipedia.org/wiki/Double_precision_floating-point_format. –

15

Ponieważ są one wdrożenie, są wolne, aby jak najwięcej założenia jak chcą. Znają format double i mogą zamiast tego używać sztuczek.

Prawdopodobnie (jak w prawie nie ma nawet pytania), Twój double to binary64 format. Oznacza to, że znak ma swój własny bit, a wartość bezwzględna jest po prostu czyszczeniem tego. Na przykład, jak specjalizacji, realizator kompilator może wykonać następujące czynności:

template <> 
double abs<double>(const double x) 
{ 
    // breaks strict aliasing, but compiler writer knows this behavior for the platform 
    uint64_t i = reinterpret_cast<const std::uint64_t&>(x); 
    i &= 0x7FFFFFFFFFFFFFFFULL; // clear sign bit 

    return reinterpret_cast<const double&>(i); 
} 

Usuwa rozgałęzienia i mogą działać szybciej.

+0

Czy nie byłoby to rażącym przykładem niezdefiniowanego zachowania? – jpalecek

+5

@jpalecek: Nie widzę tam żadnego UB. 'reinterpret_cast' ma zdefiniowane przez implementację zachowanie. (Stąd mój punkt widzenia, "mogą oni dokonać wszelkich konkretnych założeń dotyczących implementacji, których chcą, ponieważ są one implementacją.") Kodery nie-biblioteki nie mogą tego zrobić. Kiedy napiszesz ten kod, zakładasz pewne rzeczy dotyczące twojej implementacji. – GManNickG

+1

Jeśli double nie jest IEEE-754, tak. Jest to co najmniej mniej przenośne –

3

Nie może być kilka rzeczy:

  • jesteś pewien pierwsze wywołanie używa std::abs? To może równie dobrze korzystać z całkowitą abs z C (albo zadzwonić std::abs jawnie, lub using std::abs;)

  • kompilator może mieć wewnętrzną realizację niektórych funkcji float (np. Kompilować je bezpośrednio do instrukcji FPU)

Jednak jestem zaskoczony, że kompilator nie eliminuje w ogóle pętli - ponieważ nie robisz niczego z żadnym efektem w pętli, a przynajmniej w przypadku abs, kompilator powinien wiedzieć, że nie ma strony ruchomości.

8

Istnieją dobrze znane sztuczki do obliczania wartości bezwzględnej liczby podpisanej uzupełnienia dwójki. Jeśli liczba jest ujemna, odwróć wszystkie bity i dodaj 1, czyli xor z -1 i odejmij -1. Jeśli jest dodatnia, nie rób nic, to znaczy xor z 0 i odejmij 0.

int my_abs(int x) 
{ 
    int s = x >> 31; 
    return (x^s) - s; 
} 
+0

Wartości zmiennoprzecinkowe nie są przechowywane w dopełnieniu dwójki, ale jest to sprytna sztuczka dla wartości bezwzględnej. (Dla określenia znaku, robię ten sam pierwszy krok, ale potem zwracam 's | 1', -1 = negatyw, +1 = pozytywny) –

+0

To jest dobre, choć poza tematem tutaj ... – jpalecek

+0

@jpal Wystarczająco fair, ale gdzie dokładnie jest stan moka, interesują go tylko typy zmiennoprzecinkowe? Może "szablon " powinien zostać zamieniony na 'template ' wtedy, żeby to wyjaśnić? – fredoverflow

1

Twoja wersja ABS jest inlined i może być obliczana raz kompilator może trywialnie, że wartość zwracana nie ulegnie zmianie, tak że nawet nie trzeba wywołać funkcję.

Naprawdę trzeba spojrzeć na wygenerowany kod zespołu (ustawić punkt przerwania i otworzyć "duży" widok debuggera, ten demontaż będzie w lewym dolnym rogu, jeśli pamięć służy), a następnie można zobaczyć, co się dzieje.

Możesz znaleźć dokumentację na temat procesora online bez większych problemów, powie Ci ona, jakie są wszystkie instrukcje, abyś mógł dowiedzieć się, co się dzieje. Możesz też wkleić go tutaj, a powiemy Ci. ;)

8

Jaki jest twój kompilator i ustawienia? Jestem pewien, że MS i GCC implementują "wewnętrzne funkcje" dla wielu operacji matematycznych i łańcuchowych.

linię:

printf("%.3f", abs(1.25)); 

wpada do następujących "Fab" ścieżkę kodu (w msvcr90d.dll)

004113DE sub   esp,8 
004113E1 fld   qword ptr [[email protected] (415748h)] 
004113E7 fstp  qword ptr [esp] 
004113EA call  abs (4110FFh) 

abs wywołać REALIZACJI C wykonania 'Fab' na MSVCR90D (raczej duży):

102F5730 mov   edi,edi 
102F5732 push  ebp 
102F5733 mov   ebp,esp 
102F5735 sub   esp,14h 
102F5738 fldz    
102F573A fstp  qword ptr [result] 
102F573D push  0FFFFh 
102F5742 push  133Fh 
102F5747 call  _ctrlfp (102F6140h) 
102F574C add   esp,8 
102F574F mov   dword ptr [savedcw],eax 
102F5752 movzx  eax,word ptr [ebp+0Eh] 
102F5756 and   eax,7FF0h 
102F575B cmp   eax,7FF0h 
102F5760 jne   fabs+0D2h (102F5802h) 
102F5766 sub   esp,8 
102F5769 fld   qword ptr [x] 
102F576C fstp  qword ptr [esp] 
102F576F call  _sptype (102F9710h) 
102F5774 add   esp,8 
102F5777 mov   dword ptr [ebp-14h],eax 
102F577A cmp   dword ptr [ebp-14h],1 
102F577E je   fabs+5Eh (102F578Eh) 
102F5780 cmp   dword ptr [ebp-14h],2 
102F5784 je   fabs+77h (102F57A7h) 
102F5786 cmp   dword ptr [ebp-14h],3 
102F578A je   fabs+8Fh (102F57BFh) 
102F578C jmp   fabs+0A8h (102F57D8h) 
102F578E push  0FFFFh 
102F5793 mov   ecx,dword ptr [savedcw] 
102F5796 push  ecx 
102F5797 call  _ctrlfp (102F6140h) 
102F579C add   esp,8 
102F579F fld   qword ptr [x] 
102F57A2 jmp   fabs+0F8h (102F5828h) 
102F57A7 push  0FFFFh 
102F57AC mov   edx,dword ptr [savedcw] 
102F57AF push  edx 
102F57B0 call  _ctrlfp (102F6140h) 
102F57B5 add   esp,8 
102F57B8 fld   qword ptr [x] 
102F57BB fchs    
102F57BD jmp   fabs+0F8h (102F5828h) 
102F57BF mov   eax,dword ptr [savedcw] 
102F57C2 push  eax 
102F57C3 sub   esp,8 
102F57C6 fld   qword ptr [x] 
102F57C9 fstp  qword ptr [esp] 
102F57CC push  15h 
102F57CE call  _handle_qnan1 (102F98C0h) 
102F57D3 add   esp,10h 
102F57D6 jmp   fabs+0F8h (102F5828h) 
102F57D8 mov   ecx,dword ptr [savedcw] 
102F57DB push  ecx 
102F57DC fld   qword ptr [x] 
102F57DF fadd  qword ptr [[email protected] (1022CF68h)] 
102F57E5 sub   esp,8 
102F57E8 fstp  qword ptr [esp] 
102F57EB sub   esp,8 
102F57EE fld   qword ptr [x] 
102F57F1 fstp  qword ptr [esp] 
102F57F4 push  15h 
102F57F6 push  8  
102F57F8 call  _except1 (102F99B0h) 
102F57FD add   esp,1Ch 
102F5800 jmp   fabs+0F8h (102F5828h) 
102F5802 mov   edx,dword ptr [ebp+0Ch] 
102F5805 and   edx,7FFFFFFFh 
102F580B mov   dword ptr [ebp-0Ch],edx 
102F580E mov   eax,dword ptr [x] 
102F5811 mov   dword ptr [result],eax 
102F5814 push  0FFFFh 
102F5819 mov   ecx,dword ptr [savedcw] 
102F581C push  ecx 
102F581D call  _ctrlfp (102F6140h) 
102F5822 add   esp,8 
102F5825 fld   qword ptr [result] 
102F5828 mov   esp,ebp 
102F582A pop   ebp 
102F582B ret 

W trybie zwolnienia używana jest instrukcja FUPS FUW (trwa 1 cykl o ylko na FPU> = Pentium), wyjście dissasembly jest: abs funkcja

00401006 fld   qword ptr [[email protected] (402100h)] 
0040100C sub   esp,8 
0040100F fabs    
00401011 fstp  qword ptr [esp] 
00401014 push  offset string "%.3f" (4020F4h) 
00401019 call  dword ptr [__imp__printf (4020A0h)] 
1

Biblioteka działa na całkowite oczywiście podczas testowania pływaków. Oznacza to, że wywołanie funkcji abs z argumentem float wymaga konwersji z float na int (może być no-op, ponieważ używasz stałej, a kompilator może to zrobić w czasie kompilacji), a następnie INTEGER abs operacji i konwersji int-> float. Funkcja szablonowa będzie wymagać operacji na pływających elementach i to prawdopodobnie robi różnicę.