Doppelte Werte speichern eine höhere Genauigkeit und sind doppelt so groß wie ein Float. Sind Intel-CPUs jedoch für Floats optimiert?
Das heißt, sind doppelte Operationen genauso schnell oder schneller als Float-Operationen für +, -, * und /?
Ändert sich die Antwort für 64-Bit-Architekturen?
c++
performance
x86
intel
osx-snow-leopard
Brent Faust
quelle
quelle
Antworten:
Es gibt keine einzige "Intel-CPU", insbesondere im Hinblick darauf, welche Vorgänge in Bezug auf andere optimiert sind! Die meisten von ihnen sind jedoch auf CPU-Ebene (speziell innerhalb der FPU) so, dass die Antwort auf Ihre Frage lautet:
ist "Ja" - innerhalb der CPU , mit Ausnahme von Division und SQL, die für etwas langsamer sind
double
als fürfloat
. (Angenommen, Ihr Compiler verwendet SSE2 für skalare FP-Mathematik, wie es alle x86-64-Compiler tun, und einige 32-Bit-Compiler, abhängig von den Optionen. Legacy x87 hat keine unterschiedlichen Breiten in Registern, nur im Speicher (es wird beim Laden / Speichern konvertiert ), also waren historisch gesehen sogar sqrt und Division fürdouble
) genauso langsam .Zum Beispiel hat Haswell einen
divsd
Durchsatz von einem pro 8 bis 14 Zyklen (datenabhängig), aber einendivss
(skalaren Einzel-) Durchsatz von einem pro 7 Zyklen. x87fdiv
ist ein Durchsatz von 8 bis 18 Zyklen. (Zahlen von https://agner.org/optimize/ . Die Latenz korreliert mit dem Durchsatz für die Division, ist jedoch höher als die Durchsatzzahlen.)Die
float
Versionen vieler Bibliotheksfunktionen mögenlogf(float)
undsinf(float)
werden auch schneller alslog(double)
und seinsin(double)
, da sie viel weniger Präzision haben, um richtig zu werden. Sie können Polynomnäherungen mit weniger Termen verwenden, um die volle Genauigkeit fürfloat
vs.double
Allerdings , deutlich impliziert die doppelten Speicher für jede Nummer Aufnahme schwere Last auf dem Cache (s) und mehr Speicherbandbreite zu füllen und diese Cache - Zeilen von / bis RAM zu verschütten; Die Zeit, die Sie für die Leistung einer Gleitkommaoperation benötigen, ist, wenn Sie viele solcher Operationen ausführen. Daher sind die Überlegungen zu Speicher und Cache von entscheidender Bedeutung.
@ Richards Antwort weist darauf hin, dass es auch andere Möglichkeiten gibt, FP-Operationen auszuführen (die SSE / SSE2-Anweisungen; gutes altes MMX war nur Ganzzahlen), insbesondere geeignet für einfache Operationen mit vielen Daten ("SIMD", Einzelanweisung / Mehrfachdaten) ) wobei jedes Vektorregister 4 Floats mit einfacher oder nur 2 Floats mit doppelter Genauigkeit packen kann , sodass dieser Effekt noch deutlicher wird.
Am Ende haben Sie Benchmarks, aber meine Prognose ist , dass für eine vernünftig (dh groß ;-) Benchmarks, Sie Vorteil Kleben mit einfacher Genauigkeit finden (natürlich unter der Annahme , dass Sie nicht brauchen , um die zusätzlichen Bits Präzision!-).
quelle
Wenn alle Gleitkommaberechnungen innerhalb der FPU ausgeführt werden, gibt es keinen Unterschied zwischen einer
double
Berechnung und einerfloat
Berechnung, da die Gleitkommaoperationen im FPU-Stapel tatsächlich mit einer Genauigkeit von 80 Bit ausgeführt werden. Einträge des FPU-Stapels werden entsprechend gerundet, um das 80-Bit-Gleitkommaformat in das Gleitkommaformatdouble
oder dasfloat
Gleitkommaformat zu konvertieren. Das Verschieben vonsizeof(double)
Bytes zum / vom RAM gegenübersizeof(float)
Bytes ist der einzige Geschwindigkeitsunterschied.Wenn Sie jedoch über eine vektorisierbare Berechnung verfügen, können Sie mit den SSE-Erweiterungen vier
float
Berechnungen gleichzeitig mit zweidouble
Berechnungen ausführen . Daher kann eine geschickte Verwendung der SSE-Anweisungen und der XMM-Register einen höheren Durchsatz bei Berechnungen ermöglichen, die nurfloat
s verwenden.quelle
Ein weiterer zu berücksichtigender Punkt ist, wenn Sie die GPU (die Grafikkarte) verwenden. Ich arbeite mit einem Projekt, das numerisch intensiv ist, aber wir brauchen nicht die Präzision, die das Doppelte bietet. Wir verwenden GPU-Karten, um die Verarbeitung weiter zu beschleunigen. CUDA-GPUs benötigen ein spezielles Paket, um Double zu unterstützen, und die Menge an lokalem RAM auf einer GPU ist recht schnell, aber recht knapp. Infolgedessen verdoppelt die Verwendung von float auch die Datenmenge, die wir auf der GPU speichern können.
Ein weiterer Punkt ist die Erinnerung. Floats benötigen halb so viel RAM wie Double. Wenn Sie mit SEHR großen Datenmengen arbeiten, kann dies ein wirklich wichtiger Faktor sein. Wenn Sie double verwenden, bedeutet dies, dass Sie zwischen Festplatte und reinem RAM zwischenspeichern müssen, ist Ihr Unterschied enorm.
Für die Anwendung, mit der ich arbeite, ist der Unterschied sehr wichtig.
quelle
Ich möchte nur zu den bereits vorhandenen großartigen Antworten hinzufügen, dass die
__m256?
Familie der SIMD- C ++ - intrinsischen Funktionen ( Same -Instruction-Multiple-Data ) entweder 4double
s parallel (z. B._mm256_add_pd
) oder 8float
s parallel (z_mm256_add_ps
. B. ) arbeitet.Ich bin nicht sicher, ob dies zu einer tatsächlichen Beschleunigung führen kann, aber es scheint möglich zu sein, 2x so viele Floats pro Befehl zu verarbeiten, wenn SIMD verwendet wird.
quelle
In Experimenten zum Hinzufügen von 3,3 für 2000000000-mal sind die Ergebnisse:
Summation time in s: 2.82 summed value: 6.71089e+07 // float Summation time in s: 2.78585 summed value: 6.6e+09 // double Summation time in s: 2.76812 summed value: 6.6e+09 // long double
Double ist also schneller und standardmäßig in C und C ++. Es ist portabler und die Standardeinstellung für alle C- und C ++ - Bibliotheksfunktionen. Alos Double hat eine deutlich höhere Präzision als Float.
Sogar Stroustrup empfiehlt Double Over Float:
"Die genaue Bedeutung von Einzel-, Doppel- und erweiterter Genauigkeit ist implementierungsdefiniert. Die Auswahl der richtigen Genauigkeit für ein Problem, bei dem die Auswahl von Bedeutung ist, erfordert ein umfassendes Verständnis der Gleitkommaberechnung. Wenn Sie dieses Verständnis nicht haben, holen Sie sich Ratschläge, nehmen Sie sich Zeit zum Lernen oder verwenden Sie Double und hoffen Sie auf das Beste. "
Vielleicht ist der einzige Fall, in dem Sie float anstelle von double verwenden sollten, 64-Bit-Hardware mit einem modernen gcc. Weil der Schwimmer kleiner ist; double ist 8 Bytes und float ist 4 Bytes.
quelle
double
schwierig zu sagen, dass Stroustrup dort empfiehlt, wenn er RTFM tatsächlich empfiehlt.Die einzig wirklich nützliche Antwort lautet: Nur Sie können es sagen. Sie müssen Ihre Szenarien vergleichen. Kleine Änderungen der Befehls- und Speichermuster können erhebliche Auswirkungen haben.
Es ist sicherlich wichtig, ob Sie Hardware vom Typ FPU oder SSE verwenden (erstere erledigt ihre gesamte Arbeit mit erweiterter 80-Bit-Genauigkeit, sodass Double näher ist; später sind es nativ 32-Bit, dh Float).
Update: s / MMX / SSE / wie in einer anderen Antwort angegeben.
quelle
Gleitkomma ist normalerweise eine Erweiterung der Allzweck-CPU. Die Geschwindigkeit hängt daher von der verwendeten Hardwareplattform ab. Wenn die Plattform Gleitkomma unterstützt, werde ich überrascht sein, wenn es einen Unterschied gibt.
quelle
Zusätzlich einige reale Daten eines Benchmarks, um einen Einblick zu erhalten:
For Intel 3770k, GCC 9.3.0 -O2 [3] Run on (8 X 3503 MHz CPU s) CPU Caches: L1 Data 32 KiB (x4) L1 Instruction 32 KiB (x4) L2 Unified 256 KiB (x4) L3 Unified 8192 KiB (x1) -------------------------------------------------------------------- Benchmark Time CPU Iterations -------------------------------------------------------------------- BM_FloatCreation 0.281 ns 0.281 ns 1000000000 BM_DoubleCreation 0.284 ns 0.281 ns 1000000000 BM_Vector3FCopy 0.558 ns 0.562 ns 1000000000 BM_Vector3DCopy 5.61 ns 5.62 ns 100000000 BM_Vector3F_CopyDefault 0.560 ns 0.546 ns 1000000000 BM_Vector3D_CopyDefault 5.57 ns 5.56 ns 112178768 BM_Vector3F_Copy123 0.841 ns 0.817 ns 897430145 BM_Vector3D_Copy123 5.59 ns 5.42 ns 112178768 BM_Vector3F_Add 0.841 ns 0.834 ns 897430145 BM_Vector3D_Add 5.59 ns 5.46 ns 100000000 BM_Vector3F_Mul 0.842 ns 0.782 ns 897430145 BM_Vector3D_Mul 5.60 ns 5.56 ns 112178768 BM_Vector3F_Compare 0.840 ns 0.800 ns 897430145 BM_Vector3D_Compare 5.61 ns 5.62 ns 100000000 BM_Vector3F_ARRAY_ADD 3.25 ns 3.29 ns 213673844 BM_Vector3D_ARRAY_ADD 3.13 ns 3.06 ns 224357536
wo Operationen auf 3 float (F) oder 3 double (D) verglichen werden und - BM_Vector3XCopy die reine Kopie eines (1,2,3) initialisierten Vektors ist, der vor dem Kopieren nicht wiederholt wird, - BM_Vector3X_CopyDefault mit Standardinitialisierung, die bei jeder Kopie wiederholt wird, - BM_Vector3X_Copy123 mit wiederholter Initialisierung von (1,2,3),
Vergleichsprüfungen auf Gleichheit zweier initialisierter Vektoren,
ARRAY_ADD Fasst Vektor (1,2,3) + Vektor (3,4,5) + Vektor (6,7,8) über std :: valarray zusammen, was in meinem Fall zu SSE-Anweisungen führt.
Denken Sie daran, dass dies isolierte Tests sind und die Ergebnisse je nach Compiler-Einstellungen von Maschine zu Maschine oder von Architektur zu Architektur unterschiedlich sind. Bei Caching (Problemen) und realen Anwendungsfällen kann dies völlig anders sein. Die Theorie kann sich also stark von der Realität unterscheiden. Der einzige Weg, dies herauszufinden, ist ein praktischer Test wie mit Google-Benchmark [1] und das Überprüfen des Ergebnisses der Compiler-Ausgabe für Ihre spezielle Problemlösung [2].
quelle
float
in eine bestimmte Cache-Ebene passen, währenddouble
dies nicht der Fall ist? Wenn Sie nur an die Speicherbandbreite in der gleichen Cache-Ebene gebunden wären, würden Sie in den meisten Fällen einen einfachen Faktor von 2 Unterschieden erwarten. Oder werden mehr dieser Ergebnisse für einen einzelnen "Vektor" von 3 Werten zusammenhängend gespeichert, nicht SIMD-freundlich und nicht über ein großes Array abgeschrieben? Was für ein schrecklicher Asm hat GCC gemacht, der dazu führte, dass das Kopieren ein paar Zyklen für 3 Floats dauerte, aber das 10-fache für 3 Double?