Warum optimiert Clang 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);
}
(Markieren als C und C ++, da ich gerne wissen möchte, ob die Antwort für jeden unterschiedlich ist.)
c++
c
optimization
floating-point
clang
user541686
quelle
quelle
-O3
, ich weiß nicht, wie ich überprüfen soll, was das aktiviert.static double arr[N]
ist in C nicht erlaubt;const
Variablen zählen nicht als konstante Ausdrücke in dieser SpracheAntworten:
Der IEEE 754-2008-Standard für Gleitkomma-Arithmetik und der ISO / IEC 10967- Standard für sprachunabhängige Arithmetik (LIA), Teil 1, beantworten, warum dies so ist.
Der Fall der Hinzufügung
Unter dem Standardrundungsmodus (Round-to-Nearest, Krawatten-to-Even) , sehen wir , dass
x+0.0
produziertx
, außer wennx
ist-0.0
: In diesem Fall wir eine Summe von zwei Operanden mit entgegengesetzten Vorzeichen , deren Summe haben gleich Null ist , und § 6.3 Absatz 3 Regeln, die dieser Zusatz erzeugt+0.0
.Da
+0.0
es nicht bitweise mit dem Original identisch-0.0
ist und dies-0.0
ein legitimer Wert ist, der als Eingabe auftreten kann, muss der Compiler den Code eingeben, der potenzielle negative Nullen in transformiert+0.0
.Die Zusammenfassung: Im Standardrundungsmodus in
x+0.0
, wennx
-0.0
, dann istx
selbst ein akzeptabler Ausgabewert.-0.0
, dann muss der Ausgabewert sein+0.0
, der nicht bitweise identisch ist mit-0.0
.Der Fall der Multiplikation
Im Standardrundungsmodus tritt kein solches Problem auf
x*1.0
. Wennx
:x*1.0 == x
.+/- infinity
, dann hat das Ergebnis+/- infinity
das gleiche Vorzeichen.ist
NaN
dann nachwas bedeutet , dass der Exponent und Mantisse (wenn auch nicht das Zeichen) von
NaN*1.0
werden empfohlen , um vom Eingang unverändertNaN
. Das Zeichen ist gemäß §6.3p1 oben nicht spezifiziert, aber eine Implementierung kann spezifizieren, dass es mit der Quelle identisch istNaN
.+/- 0.0
, dann ist das Ergebnis ein0
mit seinem Vorzeichenbit XORed mit dem Vorzeichenbit von1.0
, in Übereinstimmung mit §6.3p2. Da das Vorzeichenbit von1.0
ist0
, bleibt der Ausgabewert gegenüber dem Eingang unverändert. Somit istx*1.0 == x
auch dann, wennx
eine (negative) Null ist.Der Fall der Subtraktion
Im Standardrundungsmodus ist die Subtraktion
x-0.0
ebenfalls ein No-Op, da sie äquivalent zu istx + (-0.0)
. Wennx
jaNaN
, dann gelten §6.3p1 und §6.2.3 ähnlich wie für Addition und Multiplikation.+/- infinity
, dann hat das Ergebnis+/- infinity
das gleiche Vorzeichen.x-0.0 == x
.-0.0
, dann haben wir nach §6.3p2 " [...] das Vorzeichen einer Summe oder einer Differenz x - y, die als Summe x + (−y) betrachtet wird, von höchstens einem der Vorzeichen der Addenden; ". Dies zwingt uns,-0.0
als Ergebnis von zuzuweisen(-0.0) + (-0.0)
, da-0.0
sich das Vorzeichen von keinem der Addenden unterscheidet, während+0.0
sich das Vorzeichen von zwei der Addenden unterscheidet, was gegen diese Klausel verstößt.+0.0
, dann reduziert sich dies auf den(+0.0) + (-0.0)
oben in The Case of Addition betrachteten Additionsfall , der nach §6.3p3 zu geben gilt+0.0
.Da in allen Fällen der Eingabewert als Ausgabe zulässig ist, ist es zulässig,
x-0.0
ein No-Op undx == x-0.0
eine Tautologie zu berücksichtigen .Wertverändernde Optimierungen
Der IEEE 754-2008 Standard hat das folgende interessante Zitat:
Da alle NaNs und alle Unendlichkeiten denselben Exponenten haben und das korrekt gerundete Ergebnis von
x+0.0
undx*1.0
für endlichx
genau die gleiche Größe hat wiex
, ist ihr Exponent der gleiche.sNaNs
Signalisierende NaNs sind Gleitkomma-Trap-Werte. Dies sind spezielle NaN-Werte, deren Verwendung als Gleitkommaoperand zu einer ungültigen Operationsausnahme (SIGFPE) führt. Wenn eine Schleife, die eine Ausnahme auslöst, optimiert würde, würde sich die Software nicht mehr gleich verhalten.
Wie user2357112 in den Kommentaren ausführt , lässt der C11-Standard das Verhalten der Signalisierung von NaNs (
sNaN
) explizit undefiniert , sodass der Compiler davon ausgehen kann, dass sie nicht auftreten und die von ihnen ausgelösten Ausnahmen auch nicht auftreten. Der C ++ 11-Standard lässt die Beschreibung eines Verhaltens zum Signalisieren von NaNs aus und lässt es daher auch undefiniert.Rundungsmodi
In alternativen Rundungsmodi können sich die zulässigen Optimierungen ändern. Beispielsweise wird im Modus " Rund auf Negativ-Unendlich" die Optimierung
x+0.0 -> x
zulässig, jedochx-0.0 -> x
verboten.Um zu verhindern, dass GCC Standardrundungsmodi und -verhalten annimmt, kann das experimentelle Flag
-frounding-math
an GCC übergeben werden.Fazit
Clang und GCC
-O3
bleiben auch bei IEEE-754-konform. Dies bedeutet, dass die oben genannten Regeln des IEEE-754-Standards eingehalten werden müssen.x+0.0
ist nicht Bit-identisch zux
allen fürx
unter diesen Regeln, aberx*1.0
so gewählt werden kann : Nämlich, wenn wirx
eines NaN unverändert weiterzugeben .* 1.0
.x
es sich nicht um ein NaN handelt.Um die IEEE-754-unsichere Optimierung zu aktivieren
(x+0.0) -> x
, muss das Flag-ffast-math
an Clang oder GCC übergeben werden.quelle
x += 0.0
ist kein NOOP wennx
ist-0.0
. Der Optimierer könnte ohnehin die gesamte Schleife entfernen, da die Ergebnisse jedoch nicht verwendet werden. Im Allgemeinen ist es schwer zu sagen, warum ein Optimierer die Entscheidungen trifft, die er trifft.quelle
x += 0.0
es kein No-Op ist, aber ich dachte, dass dies wahrscheinlich nicht der Grund ist, weil die gesamte Schleife so oder so optimiert werden sollte. Ich kann es kaufen, es ist einfach nicht so überzeugend, wie ich gehofft hatte ...long long
der Optimierung in Kraft ist (hat es mit gcc gemacht, das sich mindestens für double gleich verhält )long long
ist ein integraler Typ, kein IEEE754-Typ.x -= 0
, ist es das gleiche?