Ist die Verwendung von double schneller als float?


71

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?


Es kommt darauf an, was Sie mit ihnen machen. Theoretisch könnte Speicherbandbreite hinzukommen. Haben Sie weitere Informationen?
Kieren Johnstone

Antworten:


79

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:

sind doppelte Operationen genauso schnell oder schneller als Float-Operationen für +, -, * und /?

ist "Ja" - innerhalb der CPU , mit Ausnahme von Division und SQL, die für etwas langsamer sind doubleals 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ür double) genauso langsam .

Zum Beispiel hat Haswell einen divsdDurchsatz von einem pro 8 bis 14 Zyklen (datenabhängig), aber einen divss(skalaren Einzel-) Durchsatz von einem pro 7 Zyklen. x87 fdivist 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 floatVersionen vieler Bibliotheksfunktionen mögen logf(float)und sinf(float)werden auch schneller als log(double)und sein sin(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ür floatvs.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!-).


1
Dies würde auch von der Cache-Blockgröße abhängen, richtig? Wenn Ihr Cache 64-Bit- oder größere Blöcke abruft, ist ein Double genauso effizient (wenn nicht sogar schneller) als ein Float, zumindest was das Lesen / Schreiben des Speichers betrifft.
Rasiermesser Sturm

5
@Razor Wenn Sie genau so viele Floats arbeiten, wie in eine Cache-Zeile passen, muss die CPU zwei Cache-Zeilen abrufen, wenn Sie stattdessen Doubles verwendet haben. Der Caching-Effekt, den ich beim Lesen von Alex 'Antwort im Sinn hatte, ist jedoch: Ihr Satz von Floats passt in Ihren Cache der n-ten Ebene, der entsprechende Satz von Doubles jedoch nicht. In diesem Fall wird die Leistung erheblich gesteigert, wenn Sie Floats verwenden.
Peter G.

@Peter, ja, das macht Sinn, sagen wir, Sie haben eine 32-Bit-Cacheline, die Verwendung von Doubles müsste jedes Mal zweimal abgerufen werden.
Rasiermesser Sturm

1
@Razor, das Problem besteht nicht wirklich darin, nur einen Wert abzurufen / zu speichern. Wie der Fokus von @ Peter richtig anzeigt, rufen Sie häufig "mehrere" Werte ab, mit denen gearbeitet werden soll (ein Array von Zahlen wäre ein typisches Beispiel). und Operationen an Elementen solcher Arrays, die in numerischen Anwendungen sehr häufig sind). Es gibt Gegenbeispiele (z. B. einen mit Zeigern verbundenen Baum, in dem jeder Knoten nur eine Nummer und viele andere Dinge hat: Wenn diese Nummer dann 4 oder 8 Bytes beträgt, ist das ziemlich unwichtig), weshalb ich das in der Ende muss man Benchmarking, aber die Idee trifft oft zu.
Alex Martelli

@ Alex Martelli, ich verstehe. Das macht Sinn.
Rasiermesser Sturm

27

Wenn alle Gleitkommaberechnungen innerhalb der FPU ausgeführt werden, gibt es keinen Unterschied zwischen einer doubleBerechnung und einer floatBerechnung, 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 Gleitkommaformat doubleoder das floatGleitkommaformat zu konvertieren. Das Verschieben von sizeof(double)Bytes zum / vom RAM gegenüber sizeof(float)Bytes ist der einzige Geschwindigkeitsunterschied.

Wenn Sie jedoch über eine vektorisierbare Berechnung verfügen, können Sie mit den SSE-Erweiterungen vier floatBerechnungen gleichzeitig mit zwei doubleBerechnungen ausführen . Daher kann eine geschickte Verwendung der SSE-Anweisungen und der XMM-Register einen höheren Durchsatz bei Berechnungen ermöglichen, die nur floats verwenden.


13

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.


11

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 4 double s parallel (z. B. _mm256_add_pd) oder 8 float 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.


10

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.


3
+1 für die Mühe, einige Timings durchzuführen. Stroustrup empfiehlt jedoch nicht, 'double' zu verwenden, da es schneller ist, sondern wegen der zusätzlichen Präzision. In Bezug auf Ihren letzten Kommentar ist es durchaus möglich, dass Sie auf 32-Bit-Hardware 'double' verwenden möchten, wenn Sie diese zusätzliche Präzision mehr benötigen als Speicherplatz zu sparen. Und das führt zurück zu der Frage: Ist doppelt so schnell wie Float, selbst auf 32-Bit-Hardware mit einer modernen FPU, die 64-Bit-Berechnungen durchführt?
Brent Faust

1
Ein paar Hundertstelsekunden Unterschied scheint immer noch im Bereich experimenteller Fehler zu liegen. Vor allem, wenn es noch andere Dinge gibt (wie vielleicht eine nicht abgewickelte Schleife ...).
Imallett

4
Es ist ziemlich doubleschwierig zu sagen, dass Stroustrup dort empfiehlt, wenn er RTFM tatsächlich empfiehlt.
Sunside

1
Welche Hardware, welcher Compiler + Optionen, welcher Code? Wenn Sie alle 3 im selben Programm zeitlich festgelegt haben, erklärt die Hochlaufzeit der Taktrate, dass die erste langsamer ist. Offensichtlich haben Sie die automatische Vektorisierung nicht aktiviert (unmöglich für eine Reduzierung ohne -ffast-math oder was auch immer, da FP-Mathematik nicht streng assoziativ ist). Dies beweist also nur, dass es keinen Geschwindigkeitsunterschied gibt, wenn der Engpass eine skalare FP-Add-Latenz ist. Das Bit über 64-Bit-Hardware macht auch keinen Sinn: float ist auf jeder normalen Hardware immer halb so groß wie double. Der einzige Unterschied bei 64-Bit-Hardware besteht darin, dass x86-64 SSE2 als Basis hat.
Peter Cordes

8

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.


2

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.


-1

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),

  • Add / Mul Jeder initialisiert 3 Vektoren (1,2,3) und addiert / multipliziert den ersten und zweiten in den dritten,
  • 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].

  1. https://github.com/google/benchmark
  2. https://sourceware.org/binutils/docs/binutils/objdump.html -> objdump -S
  3. https://github.com/Jedzia/oglTemplate/blob/dd812b72d846ae888238d6f726d503485b796b68/benchmark/Playground/BM_FloatingPoint.cpp

1
Haben Sie Größen ausgewählt, die floatin eine bestimmte Cache-Ebene passen, während doubledies 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?
Peter Cordes

Das ist eine sehr gute Beobachtung, Peter. Alle theoretischen Erklärungen hier sind gültig und gut zu wissen. Meine Ergebnisse sind ein Sonderfall eines Aufbaus mit vielen verschiedenen möglichen Lösungen. Mein Punkt ist nicht, wie schrecklich meine Lösung sein mag, aber dass es in der Praxis zu viele Unbekannte gibt und Sie Ihren speziellen Anwendungsfall testen müssen, um sicherzugehen. Ich schätze Ihre Analyse. Das hilft mir :) Aber konzentrieren wir uns auf die Frage, die vom OP gestellt wird.
Jedzia

Ok, das ist fair, es ist interessant zu demonstrieren, dass Compiler ohne ersichtlichen Grund völlig saugen können, wenn Sie float in double ändern. Sie sollten vielleicht darauf hinweisen, dass dies Ihre Antwort zeigt, nicht irgendein grundlegendes Problem oder ein allgemeiner Fall.
Peter Cordes

Der Schuldige hier bin natürlich ich. Mit meinem teuflischen Gebrauch von "flüchtig". Der Compiler hat keine Chance, etwas zu optimieren, was auch mein Ziel für diesen Sonderfall war. Also beurteilen Sie GCC nicht zu hart :)
Jedzia

Um eine Hintergrundgeschichte hinzuzufügen: Ich war genauso neugierig wie das OP. Macht es einen Unterschied, Double anstelle von Float zu verwenden? Wie ich die Ergebnisse lese: Die ersten sind zu isoliert und nur die letzten beiden geben an, was in einem realen Fall zu erwarten ist -> kein Unterschied. In meinem speziellen Fall. Dank Corona hatte ich die Zeit, dieses Kaninchenloch hinunterzugehen. Diese Art der Untersuchung kann viele Stunden dauern und Sie müssen selbst entscheiden, ob sie praktikabel ist. Sagen wir für eine FPS-Verbesserung von 999 auf 1177 ...
Jedzia
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.