コンパイラ エクスプローラーをいじっていたところ、std::min に渡される引数の順序によって、出力されるアセンブリが変わることがわかりました。
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);
}
これは (たとえば、clang 9.0.0 では -O3 を使用して) 次のようにコンパイルされます。
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
std::min を旧式の三項演算子に変更しても、この問題は解決しません。また、私が試したすべての最新コンパイラ (clang、gcc、icc) でもこの問題は解決しません。
基礎となる命令は ですminsd
。ドキュメントを読むと、 の最初の引数はminsd
答えの宛先でもあります。どうやら xmm0 は私の関数が戻り値を入れる場所であるため、xmm0 が最初の引数として使用される場合は必要ありませんmovapd
。しかし、xmm0 が 2 番目の引数である場合は、値を xmm0 に入れるために が必要ですmovapd xmm0, xmm1
。(編集者注: はい、x86-64 システムVFP 引数を xmm0、xmm1 などに渡し、xmm0 で返します。
私の質問は、なぜコンパイラは引数の順序自体を切り替えないのでしょうかmovapd
。そうすれば、この必要がなくなるのでしょうか。minsd への引数の順序によって答えが変わらないことは、コンパイラは確実に知っているはずです。私が認識していない副作用があるのでしょうか。
ベストアンサー1
minsd a,b
は、いくつかの特殊なFP値に対しては可換ではないし、std::min
ただし、 を使用する場合は除きます-ffast-math
。
minsd a,b
その通りは、厳密な IEEE-754 セマンティクスで符号付きゼロと NaN に関するすべての意味を含む実装(a<b) ? a : b
です。(つまり、ソース オペランド をb
順序なしの1または等しい値に保持します)。Artyer が指摘しているように、-0.0
と は+0.0
等しい (つまり、-0. < 0.
は false) と比較されますが、それらは異なります。
std::min
比較式(a<b)
(cppref)、(a<b) ? a : b
可能な実装としては、std::fmin
これにより、どちらのオペランドからも NaN が伝播することが保証されます。(fmin
元々は C++ テンプレートではなく、C 数学ライブラリから取得されました。)
見るx86 で分岐のない FP の最小値と最大値を与える命令は何ですか?minss/minsd / maxss/maxsd (および一部の GCC バージョンを除いて同じ非可換ルールに従う対応する組み込み関数) の詳細については、こちらをご覧ください。
脚注 1: はNaN<b
任意の に対して偽でb
あり、任意の比較述語に対して偽であることを覚えておいてください。たとえば、NaN == b
は偽であり、 も同様ですNaN > b
。 もNaN == NaN
偽です。ペアの 1 つ以上が NaN の場合、それらは互いに対して「順序付けられていません」。
1つの | b | a < b | std::min(a,b) =minsd a,b |
---|---|---|---|
非N | 非N | 偽(順序なし) | a (非数) |
非N | 非NaN | 偽(順序なし) | a (非数) |
非NaN | 非N | 偽(順序なし) | a (非NaN); fmin NaNを返す |
非NaN | 非NaN | 価値観によって異なる | a またはb 、-Infに近いほど |
-0.0 | +0.0 | 偽(等しい) | a (-0.0) |
+0.0 | -0.0 | 偽(等しい) | a (+0.0) |
+-Infinity は有限値との比較では正常に機能するため、「有限」ではなく「非 NaN」と記述する必要があることに注意してください。
-ffast-math
(コンパイラにNaNやその他の仮定や近似値がないことを仮定するように指示する)と、コンパイラは意思いずれかの関数を 1 つに最適化しますminsd
。https://godbolt.org/z/a7oK91
GCCについては、https://gcc.gnu.org/wiki/FloatingPointMath-ffast-math
clang は、 catch-all を含む同様のオプションをサポートしています。
これらのオプションのいくつかは、奇妙なレガシーコードベースを除いて、ほぼすべてのユーザーが有効にする必要があります-fno-math-errno
。(推奨される数学の最適化の詳細については、このQ&Aをご覧ください。)。そして、gcc は-fno-trapping-math
デフォルトでオンになっているにもかかわらず、完全には機能しないので良いアイデアです (一部の最適化では、例外がマスクされていない場合に発生する FP 例外の数を変更でき、1 から 0 または 0 から 0 以外になることもあります、IIRC)。gcc -ftrapping-math
また、例外セマンティクスに関しても 100% 安全な最適化もいくつかブロックするため、かなり悪いです。 を使用しないコードではfenv.h
、違いがわかりません。
しかし、std::min
可換として扱うには、NaN がないことを前提とするオプションなどを使用してのみ実行できるため、決して「安全」とは言えません。NaN で何が起こるかを正確に考慮するコード用。例: -ffinite-math-only
NaN (および無限大) がないと仮定します。
clang -funsafe-math-optimizations -ffinite-math-only
必要な最適化を実行します。(unsafe-math-optimizations は、符号付きゼロのセマンティクスを考慮しないなど、より具体的なオプションを多数意味します)。