Warum Klang optimieren Weg x * 1.0, aber NICHT x + 0.0?
Warum Klappern-Optimierung entfernt die Schleife in diesem code
#include <time.h>
#include <stdio.h>
static size_t const N = 1 << 27;
static double arr[N] = { /* initialize to zero */ };
int main()
{
clock_t const start = clock();
for (int i = 0; i < N; ++i) { arr[i] *= 1.0; }
printf("%u ms\n", (unsigned)(clock() - start) * 1000 / CLOCKS_PER_SEC);
}
aber nicht die Schleife in diesem code?
#include <time.h>
#include <stdio.h>
static size_t const N = 1 << 27;
static double arr[N] = { /* initialize to zero */ };
int main()
{
clock_t const start = clock();
for (int i = 0; i < N; ++i) { arr[i] += 0.0; }
printf("%u ms\n", (unsigned)(clock() - start) * 1000 / CLOCKS_PER_SEC);
}
(Tagging als C und C++, weil ich würde gerne wissen, ob die Antwort ist für jeden anders.)
- Die Optimierungs-flags sind derzeit aktiv?
- Ich habe gerade
-O3
ich weiß nicht, wie zu überprüfen, was, der aktiviert wird, wenn. - Es wäre interessant zu sehen, was passiert, wenn Sie das add -ffast-math", um die Befehlszeile.
static double arr[N]
ist nicht zulässig, C;const
Variablen zählen nicht als Konstante Ausdrücke in dieser Spracheclang
3.6.1 kompiliert es ganz gut für mich, obwohl.- vielleicht zu einem compiler-Fehler; gcc gibt einen Fehler, auch mit den Standard-Optionen
- [Insert snarky Bemerkung über wie C ist nicht C++, auch wenn Sie bereits genannt es aus.]
- oder compiler-Erweiterung. Der code sollte
constexpr size_t N = 1 << 27;
mit C++11. - Warum MSVS nicht optimieren entfernt +0?
Du musst angemeldet sein, um einen Kommentar abzugeben.
Des IEEE 754-2008-Standard für Fließkomma-Arithmetik und die ISO/IEC 10967 Language Independent Arithmetic (LIA) Standard, Teil 1 Antwort, warum dies so ist.
Falle der Addition
Unter der Standardeinstellung (Round-to-Nearest, Krawatten-zum-Selbst), sehen wir, dass
x+0.0
produziertx
, AUßER wennx
ist-0.0
: In diesem Fall haben wir eine Summe von zwei Operanden mit entgegengesetzten Vorzeichen, deren Summe gleich null ist, und §6.3 Absatz 3 Regeln dieser Zusatz erzeugt+0.0
.Seit
+0.0
ist nicht bitweise identisch mit dem original-0.0
, und dass-0.0
ist ein legitimer Wert, der auftreten kann, die als Eingabe, der compiler ist verpflichtet, den code verwandeln möglichen negativen Nullen zu+0.0
.Zusammenfassung: Unter den Standard-Rundung-Modus, in
x+0.0
, wennx
-0.0
, dannx
selbst ist eine akzeptable Ausgabe Wert.-0.0
, dann ist die Ausgabe Wert muss+0.0
, die nicht bitweise identisch zu-0.0
.Den Fall der Multiplikation
Unter der Standardeinstellung, kein solches problem tritt mit
x*1.0
. Wennx
:x*1.0 == x
immer.+/- infinity
, dann ist das Ergebnis+/- infinity
die gleichen Zeichen.ist
NaN
, dann nachwas bedeutet, dass der exponent und die Mantisse (aber nicht die Zeichen) von
NaN*1.0
sind empfohlen werden unverändert aus der EingabeNaN
. Das Zeichen ist keine Angabe gemäß §6.3p1 oben, aber eine Implementierung angeben kann es zu identisch zu sein mit der QuelleNaN
.+/- 0.0
, dann ist das Ergebnis ein0
mit seinem Vorzeichen-bit XORed mit dem Vorzeichen-bit des1.0
im Einvernehmen mit §6.3p2. Da das Vorzeichen-bit der1.0
ist0
, die Ausgabe Wert ist unverändert aus der Eingabe. Sox*1.0 == x
auch wennx
ist ein (negativ) null ist.Der Fall der Subtraktion
Unter der Standardeinstellung, die Subtraktion
x-0.0
ist auch ein no-op, denn es entsprichtx + (-0.0)
. Wennx
istNaN
, dann §6.3p1 und §6.2.3 gelten in der gleichen Weise wie für addition und Multiplikation.+/- infinity
, dann ist das Ergebnis+/- infinity
die gleichen Zeichen.x-0.0 == x
immer.-0.0
, dann von §6.3p2 wir haben "[...] die Zeichen für eine Summe oder eine Differenz x − y als Summe x + (−y), unterscheidet sich von mindestens einer der Summanden' - Zeichen;". Dies zwingt uns zu weisen-0.0
als Ergebnis(-0.0) + (-0.0)
, weil-0.0
unterscheidet sich im Vorzeichen von keine der Summanden, während+0.0
unterscheidet sich im Vorzeichen von zwei der Summanden, die in Verletzung dieser Klausel.+0.0
, dann wird diese reduziert um den Zusatz Fall(+0.0) + (-0.0)
oben betrachtet in Falle Der Addition, die sich nach §6.3p3 regiert wird, zu geben+0.0
.Da für alle Fälle der input-Wert legal ist, als die Ausgabe, ist es zulässig zu betrachten
x-0.0
ein no-op, undx == x-0.0
eine Tautologie.Wert-Änderung Optimierungen
Des IEEE 754-2008-Standard hat das folgende interessante Zitat:
Da alle NaNs und alle Ewigkeiten teilen sich die gleiche exponent ist, und der richtig gerundete Ergebnis des
x+0.0
undx*1.0
für die finite -x
hat genau die gleiche Größe wiex
, deren exponent gleich ist.sNaNs
Signaling NaNs sind floating-point-trap-Werte; Sie sind Besondere NaN-Werte, deren Verwendung als ein floating-point-Operanden führt zu einem ungültigen Vorgang Ausnahme (SIGFPE). Wenn eine Schleife, die eine exception ausgelöst wurden optimiert sich, die software würde nicht mehr dasselbe Verhalten.
Jedoch, wie user2357112 Punkte aus in den Kommentaren, der C11-Standard explizit Blätter undefiniert das Verhalten des signaling-NaNs (
sNaN
), damit der compiler darf annehmen, dass Sie nicht auftreten, und so, dass die Ausnahmen, die Sie aufwerfen, auch nicht auftreten. Die C++11-standard unterlässt beschreibt ein Verhalten für signaling-NaNs, und somit auch lässt es undefiniert.Rundung Modi
In abwechselnden Rundung Modi der zulässigen Optimierungen ändern kann. Zum Beispiel, unter Runde-auf-Negative-Infinity - - Modus, die Optimierung
x+0.0 -> x
wird zulässig, aberx-0.0 -> x
wird verboten.Um zu verhindern, dass die GCC aus der Annahme Standard-Rundung Modi und Verhaltensweisen, die experimentelle Flagge
-frounding-math
übergeben werden kann, um GCC.Fazit
Clang und GCC, auch bei
-O3
bleibt IEEE-754-konform. Dies bedeutet, es muss halten Sie sich an die oben genannten Regeln des IEEE-754-standard.x+0.0
ist nicht bit-identisch zux
für allex
unter diesen Regeln, aberx*1.0
kann gewählt werden, so: Nämlich, wenn wirx
wenn es ist ein NaN.* 1.0
.x
ist nicht ein NaN.Aktivieren der IEEE-754-unsichere Optimierung
(x+0.0) -> x
, die Flagge-ffast-math
übergeben werden muss Clang oder GCC.sNaN
undqNaN
. That being said, die Klappern, muss sich auf diese Tatsache und unter Umständen auch die Funktion die Aufrufkonvention ist garantiert auf den Zustand der IEEE-754-control-flags. Sonst wird es Hinzugefügt haben, um die Laufzeit zu überprüfen und dynamisch entscheiden, es zu tun (Ausnahmen entlarvt) oder nicht (Ausnahmen maskiert).meaningful_nan + 1
odermeaningful_nan + other_meaningful_nan
auslösen, Ihren eigenen Umgang statt nur die Erhaltung eines der Nutzlasten. In jedem Fall ist die Sorge beschränkt sich nicht immer auf eine nicht-NaN aus einem NaN.x += 0.0
ist nicht ein NOOP undx *= 1.0
behandelt werden können, wie man ist, aber da die Ergebnisse in der Schleife nicht verwendet, das ganze könnte noch gestrippt werden, sowieso. Änderungen wiearr
lokalen könnten wir der Optimierer erkennen, die Ergebnisse sind unbenutzt und entfernen Sie die Schleife.+0.0
fallen Sie in Konflikt mit, dass die Optimierung, für die gleiche Grund, dass-0.0
scheitert in roundTowardsPositive?(+0.0)+0.0
ist eine "genaue null-Summe", aber roundTowardsNegative erfordert-0.0
für Sie.x - 0
kann vereinfacht werden zux
. Es ist nurx + 0
das ist das problem.-0 - 0
ist die Differenz von zwei Werten mit unterschiedlichen Vorzeichen. "Wenn die Summe von zwei Operanden mit entgegengesetzten Vorzeichen (oder die Differenz von zwei Operanden, die wie Zeichen) ist genau null, das Zeichen für diese Summe (oder Differenz) wird +0". Ein anderer Weg, zu sagen, es ist-0 - 0 = -0 + -0
ist die Summe der zwei Werte mit dem gleichen Vorzeichen.x+0
dann habe ich(1 + 0)*x
arbeitete, war aber unnötig kompliziert. Und schließlich habe ich verwendetx-0
. Die einfache Lösung.-frounding-math
verbietet diese Annahme (obwohl, beachten Sie, dassman gcc
Staaten, die diese option, um die "experimental").x += 0.0
ist nicht ein NOOP wennx
ist-0.0
. Der Optimierer konnte strip aus der gesamten Schleife sowieso, da die Ergebnisse nicht verwendet werden, wenn. Im Allgemeinen ist es schwer zu sagen, warum ein Optimierer macht die Entscheidungen, die er macht.x += 0.0
ist nicht ein no-op, aber ich dachte, das ist wahrscheinlich der Grund, weil die gesamte Schleife optimiert werden sollte entweder Weg. Ich kann es kaufen, es ist einfach nicht so ganz überzeugend, wie ich hoffte...long long
die Optimierung ist in der Tat (habe es mit gcc, verhält sich das gleiche für double zumindest)x -= 0
, ist es das gleiche?