pow (NAN) ist sehr langsam

8

Was ist der Grund für die katastrophale Leistung von pow() für NaN-Werte? So weit ich trainieren kann , NaNs sollte keinen Einfluss auf die Leistung haben, wenn die Fließkomma-Mathematik mit SSE anstelle der x87 FPU durchgeführt wird.

Dies scheint für elementare Operationen zu gelten, aber nicht für pow() . Ich verglich die Multiplikation und Division eines Doppels mit Quadrieren und dann die Quadratwurzel. Wenn ich den Code unten mit g++ -lrt kompiliere, bekomme ich folgendes Ergebnis:

%Vor%

Erwartungsgemäß dauert die Berechnung von NaN wesentlich länger. Kompilieren mit g++ -lrt -msse2 -mfpmath=sse führt jedoch zu folgenden Zeiten:

%Vor%

Die Multiplikation / Division von NaN ist jetzt viel schneller (tatsächlich schneller als mit einer reellen Zahl), aber das Quadrieren und Nehmen der Quadratwurzel dauert immer noch sehr lange.

Testcode (kompiliert mit gcc 4.1.2 auf 32bit OpenSuSE 10.2 in VMWare, CPU ist ein Core i7-2620M)

%Vor%

Bearbeiten:

Leider ist mein Wissen zu diesem Thema sehr begrenzt, aber ich denke, dass die glibc pow() niemals SSE auf einem 32-Bit-System verwendet, sondern eher eine Assembly in sysdeps/i386/fpu/e_pow.S . Es gibt eine Funktion __ieee754_pow_sse2 in neueren glibc Versionen, aber es ist in sysdeps/x86_64/fpu/multiarch/e_pow.c und funktioniert daher wahrscheinlich nur auf x64. All dies ist jedoch irrelevant, denn pow() ist auch ein gcc integrierte Funktion . Eine einfache Lösung finden Sie Z Bosons Antwort .

    
dasdingonesin 24.07.2014, 08:50
quelle

4 Antworten

8

"NaNs sollten keinen Einfluss auf die Performance haben, wenn die Fließkomma-Mathematik mit SSE anstelle der x87 FPU durchgeführt wird."

Ich bin nicht sicher, dass dies aus der Ressource folgt, die du zitierst. In jedem Fall ist pow eine C-Bibliotheksfunktion. Es ist nicht als Anweisung implementiert, auch nicht auf x87. Daher gibt es hier zwei separate Probleme - wie SSE NaN -Werte behandelt und wie eine pow -Funktionsimplementierung NaN -Werte behandelt.

Wenn die Funktionsreferenz pow für spezielle Werte wie +/-Inf oder NaN einen anderen Pfad verwendet, können Sie erwarten, dass ein NaN -Wert für die Basis oder den Exponenten schnell einen Wert zurückgibt. Auf der anderen Seite wird die Implementierung dies möglicherweise nicht als separater Fall behandeln und beruht einfach auf Gleitkommaoperationen, um Zwischenergebnisse als NaN -Werte zu propagieren.

Beginnend mit "Sandy Bridge" wurden viele der mit Denormalen verbundenen Leistungseinbußen reduziert oder eliminiert. Nicht alles, denn der Autor beschreibt eine Strafe für mulps . Daher wäre es vernünftig zu erwarten, dass nicht alle arithmetischen Operationen mit NaNs "schnell" sind. Einige Architekturen könnten sogar auf Microcode zurückgreifen, um NaNs in verschiedenen Kontexten zu handhaben.

    
Brett Hale 24.07.2014, 09:34
quelle
3

Deine Mathebibliothek ist zu alt. Finde entweder eine andere Math-Bibliothek, die Pow mit NAN besser implementiert, oder implementiere einen Fix wie folgt:

%Vor%

Kompilieren Sie mit g++ -O3 -msse2 -mfpmath=sse foo.cpp .

    
Z boson 24.07.2014 10:05
quelle
2

Wenn Sie Quadrieren oder die Quadratwurzel verwenden möchten, verwenden Sie d*d oder sqrt(d) . Die pow(d,2) und pow(d,0.5) sind langsamer und möglicherweise weniger genau, es sei denn, Ihr Compiler optimiert sie basierend auf dem konstanten zweiten Argument 2 und 0,5; Beachten Sie, dass eine solche Optimierung für pow(d,0.5) nicht immer möglich ist, da sie 0.0 zurückgibt, wenn d eine negative Null ist, während sqrt(d) -0.0 zurückgibt.

Wenn Sie Timings machen, stellen Sie bitte sicher, dass Sie dasselbe testen.

    
vinc17 24.07.2014 11:06
quelle
2

Mit einer komplexen Funktion wie pow () gibt es viele Möglichkeiten, wie NaN Langsamkeit auslösen kann. Es könnte sein, dass die Operationen auf NaNs langsam sind, oder es könnte sein, dass die pow () - Implementierung alle Arten von speziellen Werten überprüft, die es effizient verarbeiten kann, und die NaN-Werte schlagen alle diese Tests fehl und führen zu einem teureren Pfad vergeben sein. Sie müssten den Code Schritt für Schritt durchgehen.

Eine neuere Implementierung von pow () könnte zusätzliche Prüfungen enthalten, um NaN effizienter zu behandeln, aber dies ist immer ein Kompromiss - es wäre eine Schande, wenn pow () 'normale' Fälle langsamer behandelt, um zu beschleunigen NaN Handhabung.

Mein Blogbeitrag wurde nur auf einzelne Anweisungen angewendet, nicht auf komplexe Funktionen wie pow ().

    
Bruce Dawson 23.02.2015 02:28
quelle

Tags und Links