Frage

Vor ein oder zwei Jahrzehnten lohnte es sich, numerischen Code zu schreiben, um Multiplikationen und Divisionen zu vermeiden und stattdessen Addition und Subtraktion zu verwenden.Ein gutes Beispiel ist die Verwendung Vorwärtsdifferenzen um eine Polynomkurve auszuwerten, anstatt das Polynom direkt zu berechnen.

Ist dies immer noch der Fall oder sind moderne Computerarchitekturen so weit fortgeschritten, dass *,/ nicht mehr um ein Vielfaches langsamer sind als +,- ?

Genauer gesagt interessiere ich mich für kompilierten C/C++-Code, der auf modernen typischen x86-Chips mit umfangreicher integrierter Gleitkomma-Hardware läuft, und nicht für einen kleinen Mikrocontroller, der versucht, FP in Software umzusetzen.Mir ist klar, dass Pipelining und andere Architekturverbesserungen bestimmte Zykluszahlen ausschließen, aber ich würde trotzdem gerne eine nützliche Vorstellung davon bekommen.

War es hilfreich?

Lösung

Es hängt auch vom Unterrichtsmix ab.Ihr Prozessor verfügt jederzeit über mehrere Recheneinheiten in Bereitschaft, und Sie erhalten den maximalen Durchsatz, wenn alle ständig belegt sind.Das Ausführen einer Mul-Schleife ist also genauso schnell wie das Ausführen einer Schleife oder von Adds – das Gleiche gilt jedoch nicht, wenn der Ausdruck komplexer wird.

Nehmen Sie zum Beispiel diese Schleife:

for(int j=0;j<NUMITER;j++) {
  for(int i=1;i<NUMEL;i++) {
    bla += 2.1 + arr1[i] + arr2[i] + arr3[i] + arr4[i] ;
  }
}

für NUMITER=10^7, NUMEL=10^2 werden beide Arrays auf kleine positive Zahlen initialisiert (NaN ist viel langsamer), dies dauert 6,0 Sekunden bei Verwendung von Doubles auf einem 64-Bit-Proc.Wenn ich die Schleife durch ersetze

bla += 2.1 * arr1[i] + arr2[i] + arr3[i] * arr4[i] ;

Es dauert nur 1,7 Sekunden...Da wir also die Zugaben „übertrieben“ hatten, waren die Muls im Wesentlichen kostenlos;und die Reduzierung der Zugänge hat geholfen.Es wird noch verwirrender:

bla += 2.1 + arr1[i] * arr2[i] + arr3[i] * arr4[i] ;

– gleiche mul/add-Verteilung, aber jetzt wird die Konstante addiert statt multipliziert – dauert 3,7 Sekunden.Ihr Prozessor ist wahrscheinlich dafür optimiert, typische numerische Berechnungen effizienter durchzuführen.Punktprodukt-ähnliche Summen von Muls und skalierte Summen sind also so gut wie es nur geht;Das Hinzufügen von Konstanten ist bei weitem nicht so häufig, daher ist es langsamer ...

bla += someval + arr1[i] * arr2[i] + arr3[i] * arr4[i] ; /*someval == 2.1*/

dauert wiederum 1,7 Sekunden.

bla += someval + arr1[i] + arr2[i] + arr3[i] + arr4[i] ; /*someval == 2.1*/

(wie die anfängliche Schleife, jedoch ohne teure Konstantenaddition:2,1 Sekunden)

bla += someval * arr1[i] * arr2[i] * arr3[i] * arr4[i] ; /*someval == 2.1*/

(hauptsächlich Muls, aber eine Ergänzung: 1,9 Sekunden)

Also im Grunde genommen;Es ist schwer zu sagen, was schneller ist, aber wenn Sie Engpässe vermeiden möchten, ist es wichtiger, eine vernünftige Mischung zu haben, NaN oder INF zu vermeiden und das Hinzufügen von Konstanten zu vermeiden.Was auch immer Sie tun, stellen Sie sicher, dass Sie verschiedene Compilereinstellungen testen, da oft schon kleine Änderungen den Unterschied ausmachen können.

Noch ein paar Fälle:

bla *= someval; // someval very near 1.0; takes 2.1 seconds
bla *= arr1[i] ;// arr1[i] all very near 1.0; takes 66(!) seconds
bla += someval + arr1[i] * arr2[i] + arr3[i] * arr4[i] ; // 1.6 seconds
bla += someval + arr1[i] * arr2[i] + arr3[i] * arr4[i] ; //32-bit mode, 2.2 seconds
bla += someval + arr1[i] * arr2[i] + arr3[i] * arr4[i] ; //32-bit mode, floats 2.2 seconds
bla += someval * arr1[i]* arr2[i];// 0.9 in x64, 1.6 in x86
bla += someval * arr1[i];// 0.55 in x64, 0.8 in x86
bla += arr1[i] * arr2[i];// 0.8 in x64, 0.8 in x86, 0.95 in CLR+x64, 0.8 in CLR+x86

Andere Tipps

Theoretisch sind die Informationen hier:

Intel®64- und IA-32 Architekturen-Optimierungsreferenzhandbuch, Anhang C Anweisungslatenz und Durchsatz

Für jeden Prozessor, den sie auflisten, ist die Latenz von FMUL der von FADD oder FDIV sehr nahe. Bei einigen älteren Prozessoren ist FDIV 2-3 Zeit langsamer als das, während neuere Prozessoren das gleiche wie FMUL.

Vorbehalte:

  1. Das Dokument, das ich verlinkt habe, sagt tatsächlich, dass Sie sich im wirklichen Leben nicht auf diese Zahlen verlassen können, da der Prozessor das tun wird, was er möchte, um die Dinge schneller zu machen, wenn es richtig ist.

  2. Es besteht eine gute Chance, dass Ihr Compiler entscheidet, eines der vielen neueren Anweisungssätze zu verwenden, bei denen ein Gleitkomma-Multiplizieren / Klassifizier verfügbar ist.

  3. Dies ist ein kompliziertes Dokument, das nur von Compiler -Autoren gelesen werden soll, und ich hätte es vielleicht falsch verstanden. Als ob ich nicht klar bin, warum die Latenzzahl der FDIV für einige CPUs vollständig fehlt.

Der beste Weg, um diese Frage zu beantworten, besteht darin, tatsächlich einen Benchmark/Profil der Verarbeitung zu schreiben, die Sie ausführen müssen. Empirisch sollte über theoretisch eingesetzt werden, wenn es möglich ist. Besonders wenn es leicht zu erreichen ist.

Wenn Sie bereits verschiedene Implementierungen der Mathematik kennen, die Sie durchführen müssen, können Sie AA einige verschiedene Code -Transfermationen der Mathematik schreiben und sehen, wo Ihre Leistung ihren Höhepunkt erreicht. Auf diese Weise kann der Prozessor/Compiler verschiedene Ausführungsströme generieren, um die Prozessorpipelines zu füllen und Ihnen eine konkrete Antwort auf Ihre Antwort zu geben.

Wenn Sie sich speziell für die Leistung von Div/MUL/Add/Sub -Typ -Anweisungen interessieren, können Sie sogar eine Inline -Baugruppe einwerfen, um speziell zu steuern, welche Varianten dieser Anweisungen ausgeführt werden. Sie müssen jedoch sicherstellen, dass Sie mehrstufige Ausführungseinheiten beschäftigt, um eine gute Vorstellung von der Leistung zu bekommen, zu der das System fähig ist.

Wenn Sie auch so etwas tun, können Sie die Leistung mit mehreren Variationen des Prozessors vergleichen, indem Sie einfach dasselbe Programm auf ihnen ausführen, und können Sie auch die Unterschiede in den Motherboards berücksichtigen.

Bearbeiten:

Grundlegende Architektur von A +- ist identisch. Es dauern also logischerweise die gleiche Zeit, um zu berechnen. * Erfordern Sie dagegen mehrere Schichten, die normalerweise aus "Vollverdienern" hergestellt wurden, um eine einzelne Operation abzuschließen. Diese Garente, dass ein * in jedem Zyklus in der Pipeline eine höhere Latenz als ein Add/Subtrahieren -Schaltkreis aufweisen kann. Ein FP / Operation wird normalerweise unter Verwendung einer Approximationsmethode implementiert, die iterativ in Richtung der richtigen Antwort im Laufe der Zeit konvergiert. Diese Arten von Näherungen werden typischerweise über Multiplikation implementiert. Für den schwimmenden Punkt können Sie im Allgemeinen annehmen, dass die Teilung länger dauert, da es unpraktisch ist, die Multiplikationen (was bereits ein großer Schaltkreis in und von sich selbst ist) in Pipeline einer Vielzahl von Multiplikator -Schaltungen zu "abzuwickeln". Dennoch wird die Leistung eines bestimmten Systems am besten durch Tests gemessen.

Ich kann keine endgültige Referenz finden, aber umfangreiche Experimente sagt mir, dass die Schwimmermultiplikation heutzutage genau die gleiche Geschwindigkeit ist wie die Addition und Subtraktion, während die Teilung nicht (aber auch nicht "langsamer" ist). Sie können die gewünschte Intuition nur durch Ausführen Ihrer eigenen Experimente erhalten. Denken Sie daran, die Zufallszahlen (Millionen davon) im Voraus zu generieren, lesen So viel wie Sie sie abhalten können) für eine genaue Messung!

Die Geschwindigkeitsdifferenz von * / vs + - hängt von Ihrer Prozessorarchitektur ab. Im Allgemeinen und mit x86 im Besonderen ist der Geschwindigkeitsunterschied bei modernen Prozessoren geringer geworden. * sollte im Zweifel nahe sein, im Zweifel: einfach experimentieren. Wenn Sie ein wirklich schweres Problem mit vielen FP -Operationen haben, sollten Sie auch Ihre GPU (GeForce, ...) verwenden, die als Vektorprozessor fungiert.

Es gibt wahrscheinlich sehr wenig Zeitunterschied zwischen Multiplikation und Addition. Die Teilung hingegen ist immer noch deutlich langsamer als die Multiplikation aufgrund ihrer rekursiven Natur. Bei modernen X86 -Architektur -SSE -Anweisungen sollten bei der Durchführung des schwebenden Punktbetriebs berücksichtigt werden, anstatt die FPU zu verwenden. Obwohl ein guter C/C ++ - Compiler Ihnen die Möglichkeit gibt, SSE anstelle der FPU zu verwenden.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top