Performance von Test gegen Null mit kleinem Epsilon für floats
-
Der Code (die version mit dem reinterpret_cast und die entsprechende Assembler-Variante) verlässt sich ja ziemlich darauf, dass Floats so repräsentiert werden, wie sie anscheinend werden. Wann kann es da zu Problemen kommen? Nur bei anderen CPUs? Sind x86er immer so? Was ist mit 64 Bit Architekturen?
Ich muss das mal auf Geschwindigkeit testen. Aber wenn das nicht soo viel bringt, fühle ich mich mit C++ Code besser ;). Oder ich könnte nen #ifdef drumstricken und hätte noch ne Alternative im #else Zweig (z.B. die return (f >= -ZERO) && (f <=ZERO) Variante). Welche Präprozessor-Symbole brauche ich dann?
-
Was mir zum fabs() einfällt: Der Profiler braucht Debugcode. Da kann das alles natürlich etwas anders ausehen...
-
Der Code verlässt sich ja ziemlich darauf, dass Floats so repräsentiert werden, wie sie anscheinend werden. Wann kann es da zu Problemen kommen?
damit's nicht funktioniert muesste deine architektur keine 32bit-floats entsprechend IEEE-norm haben.
da kenn ich so spontan - keine.
irgendwer anders?
-
auf maschinen mit little-endian byte-order dürfte das sogar funktionieren, solange die representation der gleitkommazahl selbst IEEE konform ist und die größe dieser wenigstens so groß wie int ist. d.h. z.b. eine variante für double sollte auch funktionieren, ohne dass wir deswegen einen größeren integer benötigen (dabei geht nur etwas genauigkeit beim vergleich verloren, bei so einem epsilontest spielt das keine rolle).
-
Hab mal mit vs2002 Release kompilieren lassen (sorry für den häßlichen Output):
Meine IsZero1() { return fabs(f) <= CUtils::ZERO; }:
PUBLIC ?IsZero1@@YA_NM@Z ; IsZero1 ; Function compile flags: /Ogty ; COMDAT ?IsZero1@@YA_NM@Z _TEXT SEGMENT _f$ = 8 ?IsZero1@@YA_NM@Z PROC NEAR ; IsZero1, COMDAT ; 753 : return fabs(f) <= CUtils::ZERO; fld DWORD PTR _f$[esp-4] fabs fld DWORD PTR ?ZERO@CUtils@@2MB ; CUtils::ZERO fcompp fnstsw ax test ah, 1 jne SHORT $L123904 mov eax, 1 ; 754 : }; ret 0 $L123904: ; 753 : return fabs(f) <= CUtils::ZERO; xor eax, eax ; 754 : };IsZero2() { return (f >= -CUtils::ZERO) && (f <= CUtils::ZERO); }:
PUBLIC ?IsZero2@@YA_NM@Z ; IsZero2 ; Function compile flags: /Ogty ; COMDAT ?IsZero2@@YA_NM@Z _TEXT SEGMENT _f$ = 8 ?IsZero2@@YA_NM@Z PROC NEAR ; IsZero2, COMDAT ; 774 : return (f >= -CUtils::ZERO) && (f <= CUtils::ZERO); fld DWORD PTR ?ZERO@CUtils@@2MB ; CUtils::ZERO fchs fcomp DWORD PTR _f$[esp-4] fnstsw ax test ah, 65 ; 00000041H jp SHORT $L123910 fld DWORD PTR _f$[esp-4] fcomp DWORD PTR ?ZERO@CUtils@@2MB ; CUtils::ZERO fnstsw ax test ah, 65 ; 00000041H jp SHORT $L123910 mov eax, 1 ; 775 : }; ret 0 $L123910: ; 774 : return (f >= -CUtils::ZERO) && (f <= CUtils::ZERO); xor eax, eax ; 775 : };IsZero3() {return (reinterpret_cast<const unsigned&>(f)&(~0u>>1))<reinterpret_cast<const unsigned&>(CUtils::ZERO);}
PUBLIC ?IsZero3@@YA_NABM@Z ; IsZero3 ; Function compile flags: /Ogty ; COMDAT ?IsZero3@@YA_NABM@Z _TEXT SEGMENT _f$ = 8 ?IsZero3@@YA_NABM@Z PROC NEAR ; IsZero3, COMDAT ; 778 : return (reinterpret_cast<const unsigned&>(f)&(~0u>>1))<reinterpret_cast<const unsigned&>(CUtils::ZERO); mov eax, DWORD PTR _f$[esp-4] mov ecx, DWORD PTR [eax] mov eax, DWORD PTR ?ZERO@CUtils@@2MB and ecx, 2147483647 ; 7fffffffH cmp ecx, eax sbb eax, eax neg eax ; 779 : }
-
die optimierung, die reinterpret_cast<const unsigned&>(CUtils::ZERO) wie konstanten ausdruck behandelt, wird offenbar erst durch vc++8.0 durchgeführt.
behelfsmäßig könnte man ja soetwas machen:inline IsZero3() { assert(0x38D1B717h==reinterpret_cast<const unsigned&>(CUtils::ZERO)); return (reinterpret_cast<const unsigned&>(f)&(~0u>>1))<0x38D1B717h; }und so auch mit älteren compilern besseren code erhalten. die funktion sollte im übrigen unbedingt inline sein.
-
hellihjb schrieb:
damit's nicht funktioniert muesste deine architektur keine 32bit-floats entsprechend IEEE-norm haben.
da kenn ich so spontan - keine.
irgendwer anders?camper schrieb:
auf maschinen mit little-endian byte-order dürfte das sogar funktionieren, solange die representation der gleitkommazahl selbst IEEE konform ist und die größe dieser wenigstens so groß wie int ist. d.h. z.b. eine variante für double sollte auch funktionieren, ohne dass wir deswegen einen größeren integer benötigen (dabei geht nur etwas genauigkeit beim vergleich verloren, bei so einem epsilontest spielt das keine rolle).
Ich frag ja nur, weil der Code einfach funktionieren MUSS. Da darf nicht plötzlich Mist rauskommen, nur weil ich ein bisschen mit Assembler rumgespielt habe, und keiner merkt etwas :). So einen Fehler zu finden ist ja fast unmöglich.
Wie schaut's denn aus mit meinen Präprozessor-Symbolen? Gibt's welche?
-
Welche Zahl ist denn 0x38D1B717? Auf jeden Fall stammt die nicht mein CUtils::ZERO = 1e-5f
Aber wenn ich mir meinen Assemblercode anschaue, müsste ich wohl 2147483647 nehmen, oder?
-
der wert, der bei
cout << reinterpret_cast<const unsigned&>(CUtils::ZERO);ausgegeben wird gehört dort hin.
-
Hi,
also ich habe mich nun für die Variante
inline static bool IsZero(const float& f) { return (reinterpret_cast<const unsigned&>(f)&(~0u>>1))<reinterpret_cast<const unsigned&>(ZERO); };Ich wollte aber mal fragen, ob dies hier zu Geschwindigkeitseinbußen führt, oder ob das egal ist:
inline static bool IsZero(const float& f, float epsilon = ZERO) { return (reinterpret_cast<const unsigned&>(f)&(~0u>>1))<reinterpret_cast<const unsigned&>(epsilon); };Ich dachte, da das inline ist, sollte Compiler ja für den Wert von epsilon eine Konstante annehmen können und hätte dann dieselbe Ausgangssituation, wie im ersten Beispiel. Oder?
-
FelixManke schrieb:
Hi,
also ich habe mich nun für die Variante
inline static bool IsZero(const float& f) { return (reinterpret_cast<const unsigned&>(f)&(~0u>>1))<reinterpret_cast<const unsigned&>(ZERO); };Ich wollte aber mal fragen, ob dies hier zu Geschwindigkeitseinbußen führt, oder ob das egal ist:
inline static bool IsZero(const float& f, float epsilon = ZERO) { return (reinterpret_cast<const unsigned&>(f)&(~0u>>1))<reinterpret_cast<const unsigned&>(epsilon); };Ich dachte, da das inline ist, sollte Compiler ja für den Wert von epsilon eine Konstante annehmen können und hätte dann dieselbe Ausgangssituation, wie im ersten Beispiel. Oder?
theoretisch - sicher. die frage ist ja, was in der praxis passiert. bei meinen versuchen mit vc++8.0 stellt sich heraus, das dieser compiler nur dann optimal optimiert (d.h. der int-wert von epsilon ist unmittelbar teil des op-codes), wenn epsilon static storage duration hat und durch einen konstanten ausdruck (d.h. hier ein literal) initialisiert wird. schon das weglassen des static in meinem ursprünglichen code verhindert diese optimierung - eine übergabe als funktionsparameter dürfte kein besseres ergebnis bringen. letzlich musst du dir einfach mal den assmblercode anschauen bzw. messungen durchführen, um den besten kompromiss zwischen ausführungsgeschwindigkeit und flexibilität herauszufinden.
-
hellihjb schrieb:
bzgl gcc gibt's irgendwelche flags die auch inline-assembler mit intel-syntax erlauben, hab's aber auch nicht mehr im kopf - muesste sich per suche finden lassen.
Das würde mich unglaublich interessieren. Falls es irgendjemand anderem einfällt, wäre ich sehr dankbar darüber
