1. ホーム
  2. android

std::min の引数順で浮動小数点のコンパイラ出力が変わる

2023-10-25 17:43:23

疑問点

コンパイラエクスプローラでいじっていたら、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引数であるなら、それは movapd xmm0, xmm1 でxmm0に値を取り込むことができます(編集部注:そうです。 x86-64システムV は FP の引数を xmm0, xmm1 などで渡し、xmm0 で返します)。

私の質問:なぜコンパイラは引数の順番自体を切り替えないのでしょうか?そうすれば、この movapd は必要ないのでしょうか?それはきっと、minsdへの引数の順序が答えを変えないことを知っているに違いない?私が評価していない何らかの副作用があるのでしょうか?

どのように解決するのか?

minsd a,b はいくつかの特殊なFP値に対して可換でなく、また std::min を使用しない限り -ffast-math .

minsd a,b まさに 実装 (a<b) ? a : b を実装しており、厳密なIEEE-754セマンティックスの符号付きゼロとNaNについて暗示しているすべてを含んでいます。(すなわち、それはソースオペランドを保持します。 b を、順序のない 1 またはそれに等しい)。 Artyerが指摘するように -0.0+0.0 は等しいことを比較する(つまり -0. < 0. は偽)ですが、両者は区別されます。

std::min が定義されているのは (a<b) の比較式 ( cppreference ) を使って (a<b) ? a : b とは異なり、可能な実装として std::fmin とは異なり、特にどちらかのオペランドからのNaN伝搬を保証しています。 ( fmin は元々C++のテンプレートではなく、Cの数学ライブラリから来たものです)。

参照 x86で分岐のないFPのminとmaxを与える命令は何ですか? minss/minsd / maxss/maxsd (そして、いくつかのGCCバージョンを除いて、同じ非可換ルールに従う対応する組込み関数)についてのより多くの詳細については、以下を参照してください。

脚注 1: 覚えておいてほしいのは NaN<b が偽であることを忘れないでください。 b に対して、また任意の比較述語に対して偽となります。 例えば NaN == b は偽で、同様に NaN > b . であっても NaN == NaN は偽です。 ペアのうちの1つ以上がNaNであるとき、それらは互いに"unordered"です。


-ffast-math (NaNがないと仮定することや、その他の仮定や近似をコンパイラに指示する)、コンパイラは に最適化し、どちらかの関数を単一の minsd . https://godbolt.org/z/a7oK91

GCCについては https://gcc.gnu.org/wiki/FloatingPointMath

clang は同様のオプションをサポートしています。 -ffast-math をキャッチオールとします。

これらのオプションのいくつかは、奇妙なレガシーコードベースを除いて、ほとんどすべての人が有効にすべきもので、例えば -fno-math-errno . (参照 この Q&A で推奨される数学的最適化について詳しく説明しています。 ). また、gcc -fno-trapping-math はデフォルトでオンになっているにもかかわらず、 いずれにせよ完全には機能しないからです (いくつかの最適化は、例外がマスクされていない場合に発生する FP 例外を、時には 1 から 0 や 0 から 0 以外に変更することさえ含みます、IRC です)。 gcc -ftrapping-math は、例外のセマンティクスに関してさえ100%安全であるいくつかの最適化をブロックするので、かなり悪いです。 を使わないコードでは fenv.h を使用しないコードでは、その違いは決してわからないでしょう。

しかし std::min を可換とすることは、NaNを仮定しないオプションなどでしか実現できないので、間違いなく "safe"とは呼べないでしょう。 は、NaNで何が起こるかを正確に気にするコードのために、 例えば、以下のようになります。 -ffinite-math-only はNaNがない(無限大もない)と仮定しています。

clang -funsafe-math-optimizations -ffinite-math-only は、あなたが探している最適化を行います。 (unsafe-math-optimizations は、符号付きゼロのセマンティクスを気にしないなど、より具体的なオプションの束を意味します)。