Ich habe im Compiler-Explorer herumgespielt und festgestellt, dass die Reihenfolge der an std :: min übergebenen Argumente die ausgegebene Assembly ändert.
Hier ist das Beispiel im Godbolt Compiler Explorer
double std_min_xy(double x, double y) {
return std::min(x, y);
}
double std_min_yx(double x, double y) {
return std::min(y, x);
}
Dies wird kompiliert (zum Beispiel mit -O3 in Clang 9.0.0), um:
std_min_xy(double, double): # @std_min_xy(double, double)
minsd xmm1, xmm0
movapd xmm0, xmm1
ret
std_min_yx(double, double): # @std_min_yx(double, double)
minsd xmm0, xmm1
ret
Dies bleibt bestehen, wenn ich std :: min in einen ternären Operator der alten Schule ändere. Es bleibt auch bei allen modernen Compilern bestehen, die ich ausprobiert habe (clang, gcc, icc).
Die zugrunde liegende Anweisung lautet minsd
. Beim Lesen der Dokumentation ist das erste Argument von minsd
auch das Ziel für die Antwort. Anscheinend ist xmm0 der Ort, an dem meine Funktion ihren Rückgabewert setzen soll. Wenn also xmm0 als erstes Argument verwendet wird, ist dies nicht movapd
erforderlich. Aber wenn xmm0 das zweite Argument ist, muss es movapd xmm0, xmm1
den Wert in xmm0 bekommen. (Anmerkung des Herausgebers: Ja, x86-64 System V übergibt FP-Argumente in xmm0, xmm1 usw. und gibt in xmm0 zurück.)
Meine Frage: Warum ändert der Compiler nicht die Reihenfolge der Argumente selbst, so dass dies movapd
nicht notwendig ist? Es muss sicher wissen, dass die Reihenfolge der Argumente zu minsd die Antwort nicht ändert? Gibt es eine Nebenwirkung, die ich nicht schätze?
mov
odermovaps
Anweisungen, wenn gcc die Zuweisung registriert, was Sie nach dem Inlining nicht in der Mitte einer größeren Funktion sehen würden.)minsd
Operanden hätte ändern können, um die zu speichernmovapd
. (Aber während ich lerne, kann es das nicht.)Antworten:
minsd a,b
ist für einige spezielle FP-Werte nicht kommutativ und auch nichtstd::min
, es sei denn, Sie verwenden-ffast-math
.minsd a,b
implementiert genau(a<b) ? a : b
alles, was über vorzeichenbehaftete Null und NaN in der strengen IEEE-754-Semantik impliziert. (dh es hält den Quelloperandenb
auf ungeordnet 1 oder gleich). Wie Artyer betont-0.0
und+0.0
gleich vergleicht (dh-0. < 0.
falsch ist), aber sie sind verschieden.std::min
wird als(a<b)
Vergleichsausdruck ( cppreference ) definiert, mit(a<b) ? a : b
einer möglichen Implementierung, im Gegensatz dazu,std::fmin
die unter anderem die NaN-Ausbreitung von beiden Operanden garantiert. (fmin
stammte ursprünglich aus der C-Mathematikbibliothek, nicht aus einer C ++ - Vorlage.)Siehe Was ist die Anweisung, die verzweigungslose FP min und max auf x86 gibt? Weitere Informationen zu minss / minsd / maxss / maxsd (und den entsprechenden Intrinsics, die mit Ausnahme einiger GCC-Versionen denselben nicht kommutativen Regeln folgen).
Fußnote 1: Denken Sie daran, dass dies
NaN<b
für jedesb
und für jedes Vergleichsprädikat falsch ist . zBNaN == b
ist falsch, und so istNaN > b
. AuchNaN == NaN
ist falsch. Wenn eines oder mehrere eines Paares NaN sind, sind sie "ungeordnet". gegenseitig.Mit
-ffast-math
(dem Compiler mitzuteilen , keine NaNs und andere Annahmen und Annäherungen zu übernehmen), Compiler wird entweder Funktion auf einen einzigen optimierenminsd
. https://godbolt.org/z/a7oK91Informationen zu GCC finden Sie unter https://gcc.gnu.org/wiki/FloatingPointMath.
Clang unterstützt ähnliche Optionen, auch
-ffast-math
als Sammelbegriff.Einige dieser Optionen sollten von fast jedem aktiviert werden, mit Ausnahme von seltsamen alten Codebasen, z
-fno-math-errno
. (Weitere Informationen zu empfohlenen mathematischen Optimierungen finden Sie in diesen Fragen und Antworten. ) Und gcc-fno-trapping-math
ist eine gute Idee, da es ohnehin nicht vollständig funktioniert, obwohl es standardmäßig aktiviert ist (einige Optimierungen können immer noch die Anzahl der FP-Ausnahmen ändern, die ausgelöst würden, wenn Ausnahmen entlarvt würden, manchmal sogar von 1 auf 0 oder 0 auf ungleich Null, IIRC).gcc -ftrapping-math
blockiert auch einige Optimierungen, die auch für 100% sicher sind. Ausnahmesemantik, also ist es ziemlich schlecht. In Code, der nicht verwendet wirdfenv.h
, werden Sie den Unterschied nie erfahren.Die Behandlung
std::min
als kommutativ kann jedoch nur mit Optionen erreicht werden, die keine NaNs und ähnliches voraussetzen. Daher kann sie für Code, der sich genau darum kümmert, was mit NaNs passiert , definitiv nicht als "sicher" bezeichnet werden. zB-ffinite-math-only
nimmt keinerlei NaNs (und keiner infinities)clang -funsafe-math-optimizations -ffinite-math-only
wird die Optimierung durchführen, die Sie suchen. (Unsichere mathematische Optimierungen implizieren eine Reihe spezifischerer Optionen, einschließlich der Nichtbeachtung der vorzeichenbehafteten Nullsemantik).quelle
-ffast-math
ignoriert werden, sind nicht so subtil. Ich war unangenehm überrascht, dass es optimiert wirdisnan()
fürfalse
: godbolt.org/z/zs31Ynmovapd
mit-mavx
Option behoben wird (vorausgesetzt, die Ziel-CPU unterstützt AVX), da AVX eine zerstörungsfreie Quellcodierung (3-Operanden) der Anweisungen hinzufügt.x = std::min(array[i], x)
. Sogar AVX würde ein separates Laden in einer Schleife anstelle eines Speicherquellenoperanden benötigen, da nur die 2. Quelle Speicher sein kann. (Und es konnte nicht automatisch mit einer horizontalen Minute am Ende vektorisiert werden:std::min
ist auch für FP nicht assoziativ).Bedenken Sie :
std::signbit(std::min(+0.0, -0.0)) == false && std::signbit(std::min(-0.0, +0.0)) == true
.Der einzige andere Unterschied besteht darin, dass das zweite Argument zurückgegeben werden sollte, wenn beide Argumente (möglicherweise unterschiedliche) NaNs sind.
Sie können gcc erlauben, die Argumente mithilfe der
-funsafe-math-optimizations -fno-math-errno
Optimierungen neu anzuordnen (beide aktiviert durch-ffast-math
).unsafe-math-optimizations
Ermöglicht dem Compiler, sich nicht um vorzeichenbehaftetefinite-math-only
Nullen und nicht um NaNs zu kümmernquelle
-fno-math-errno
sollte hier irrelevant sein;std::min
ist eine C ++ STL-Vorlagenfunktion, keine C-Mathematikbibliotheksfunktion, die in der Vergangenheit eine Fehlereinstellung für die NaN-Semantik wiefmin
oder hattesqrt
.-fno-math-errno
ist jedoch immer eine gute Idee, außer in historischen Codebasen, die prüfenerrno
anstatt zu verwendenfenv.h
.-funsafe-math-optimizations
allein nicht funktioniert, aber durch Hinzufügen-fno-math-errno
wird es als kommutativ optimiert. Dies kann ein clang9-Fehler sein, der möglicherweise auch eine No-NaN-Annahme impliziert, die unsichere Mathematik nicht beinhaltet? Mit clang 10.0 und mit GCC behält unsafe-math + no-math-errno immer noch den Unterschied in der Reihenfolge der Operanden bei: Ein anderer Teil von fast-math macht den Unterschied.Zur Erweiterung der bestehenden Antworten , die sagen
std::min
nicht kommutativ ist: Hier ist ein konkretes Beispiel dafür , dass zuverlässig unterscheidetstd_min_xy
vonstd_min_yx
. Godbolt:bool distinguish1() { return 1 / std_min_xy(0.0, -0.0) > 0.0; } bool distinguish2() { return 1 / std_min_yx(0.0, -0.0) > 0.0; }
distinguish1()
bewertet zu1 / 0.0 > 0.0
, dhINFTY > 0.0
odertrue
.distinguish2()
bewertet zu1 / -0.0 > 0.0
, dh-INFTY > 0.0
oderfalse
.(All dies unter IEEE Regeln, natürlich. Ich glaube nicht , die C ++ Standard Mandate , dass Compiler dieses besondere Verhalten bewahren. Ehrlich gesagt war ich überrascht , dass der Ausdruck
-0.0
ausgewertet tatsächlich zu einem negativen Null an erster Stelle!-ffinite-math-only
beseitigt diese Art, den Unterschied zu erkennen , und-ffinite-math-only -funsafe-math-optimizations
beseitigt den Unterschied im Codegen vollständig .quelle