Clangがx + 0.0ではなくx * 1.0を最適化しないのはなぜですか?


125

Clangがこのコードのループを最適化する理由

#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);
}

しかし、このコードのループではありませんか?

#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);
}

(CとC ++の両方のタグを付けるのは、答えがそれぞれ異なるかどうかを知りたいからです。)


2
現在アクティブになっている最適化フラグはどれですか?
Iwillnotexist Idonotexist 2015年

1
@IwillnotexistIdonotexist:使用したばかりですが-O3、何がアクティブになるかを確認する方法がわかりません。
user541686

2
コマンドラインに-ffast-mathを追加するとどうなるか見てみましょう。
14:47にプラグウォッシュ

static double arr[N]Cでは許可されていません。const変数はその言語の定数式としてカウントされません
MM

1
[CがC ++でないことについての
ぎこち

回答:


164

浮動小数点演算に関するIEEE 754-2008規格とISO / IEC 10967言語非依存演算(LIA)規格、パート1は、これがなぜそうなのかを回答しています。

IEEE 754§6.3符号ビット

入力または結果がNaNの場合、この規格はNaNの符号を解釈しません。ただし、ビット文字列の操作(copy、negate、abs、copySign)はNaNの結果の符号ビットを指定することに注意してください。NaNオペランドの符号ビットに基づいている場合もあります。論理述語totalOrderも、NaNオペランドの符号ビットの影響を受けます。他のすべての演算については、入力NaNが1つしかない場合や、無効な演算からNaNが生成された場合でも、この規格はNaN結果の符号ビットを指定しません。

入力も結果もNaNでない場合、積または商の符号は、オペランドの符号の排他的ORです。合計の符号、または合計x +(-y)と見なされる差x-yの符号は、最大で1つの加数の符号とは異なります。変換結果の符号、量子化演算、roundTo-Integral演算、およびroundToIntegralExact(5.3.1を参照)は、最初または唯一のオペランドの符号です。これらの規則は、オペランドまたは結果がゼロまたは無限の場合にも適用されます。

反対の符号を持つ2つのオペランドの合計(または同様の符号を持つ2つのオペランドの差)が正確にゼロの場合、その合計(または差)の符号は、roundTowardNegativeを除くすべての丸め方向属性で+0になります。その属性の下では、正確なゼロ和(または差)の符号は-0になります。ただし、x + x = x −(−x)は、xがゼロであってもxと同じ符号を保持します。

追加の場合

デフォルトの丸めモードでは (ラウンドには、最も近い、ネクタイツーであっても)、私たちはそれが見x+0.0生成xする場合を除き、xされて-0.0:その場合は、我々は合計ゼロで反対の符号を持つ2つのオペランドの合計を持っている、と§6.3段落この追加によって生成される3つのルール+0.0

以来+0.0ないビット単位元と同じ-0.0、それは-0.0、入力として発生することがあり、正当な値であり、コンパイラは、潜在的に負のゼロを変換するコードに配置する義務があります+0.0

要約:デフォルトの丸めモードの下でx+0.0x

  • でない場合 -0.0、それx自体が許容可能な出力値です。
  • -0.0ある場合、出力値はでなければなりません +0.0。これはとビットごとに同一ではありません-0.0

乗算の場合

デフォルトの丸めモードでは、このような問題は発生しませんx*1.0。の場合x

  • x*1.0 == x常に(準)通常の数です。
  • isの+/- infinity場合、結果は+/- infinity同じ符号になります。
  • NaN、次に

    IEEE 754§6.2.3 NaN伝播

    NaNオペランドをその結果に伝搬し、入力として単一のNaNを持つ操作は、宛先形式で表現できる場合、入力NaNのペイロードでNaNを生成する必要があります。

    つまり、の指数と仮数(符号は除く)は入力から変更しないことNaN*1.0推奨されますNaN。符号は、上記の6.3p1に従って指定されていませんが、実装では、ソースと同じになるように指定できますNaN

  • である+/- 0.0場合、その結果は、§6.3p2に準拠0して、符号ビットがとXORされたに1.0なります。1.0is の符号ビットなので、0出力値は入力から変化しません。したがって、x*1.0 == x場合でもx(負)はゼロです。

減算の場合

デフォルトの丸めモードでは、減算x-0.0はと同等であるため、何もしませんx + (-0.0)。もしxIS

  • isのNaN場合、§6.3p1と§6.2.3 は、加算と乗算の場合とほとんど同じ方法で適用されます。
  • isの+/- infinity場合、結果は+/- infinity同じ符号になります。
  • x-0.0 == x常に(準)通常の数です。
  • -0.0ある場合、§6.3p2によって、「[...]合計の符号、または合計x +(-y)と見なされる差x-yの符号は、最大で1つの加数の符号と異なります。」これは、米軍が割り当てること-0.0の結果として(-0.0) + (-0.0)ので、-0.0からの符号が異なるなし加数の、一方の+0.0符号が異なるから2加数のこの句に違反して、。
  • +0.0、これはまた、ケースに減少し(+0.0) + (-0.0)て上で考慮さ添加の場合 §6.3p3によって与えることを支配され、+0.0

すべての場合において、入力値は出力として正当であるためx-0.0、ノーオペレーションとx == x-0.0トートロジーを考慮することは許容されます。

価値を変える最適化

IEEE 754-2008規格には、興味深い引用があります。

IEEE 754§10.4文字通りの意味と値を変える最適化

[...]

特に、次の値を変更する変換では、ソースコードの文字通りの意味が保持されます。

  • xがゼロではなく、シグナリングNaNでなく、結果がxと同じ指数である場合に、アイデンティティプロパティ0 + xを適用します。
  • xがシグナルNaNではなく、結果がxと同じ指数である場合に、アイデンティティプロパティ1×xを適用します。
  • クワイエットNaNのペイロードまたは符号ビットを変更します。
  • [...]

すべてのNaNとすべての無限大は同じ指数を共有し、有限のx+0.0x*1.0について正しく丸められた結果はとx正確に同じ大きさなのでx、それらの指数は同じです。

sNaNs

シグナルNaNは浮動小数点トラップ値です。これらは、浮動小数点オペランドとして使用すると無効演算例外(SIGFPE)が発生する特別なNaN値です。例外をトリガーするループが最適化された場合、ソフトウェアは同じように動作しなくなります。

ただし、user2357112 がコメント指摘しているように、C11標準ではシグナルNaN(sNaN)の動作が明示的に未定義のままになっているため、コンパイラーはそれらが発生しないと想定できるため、発生する例外も発生しません。C ++ 11標準では、シグナルNaNの動作の説明が省略されているため、未定義のままになっています。

丸めモード

別の丸めモードでは、許容される最適化が変更される場合があります。たとえば、Round-to-Negative-Infinityモードでは、最適化x+0.0 -> xは許可x-0.0 -> xされますが、禁止されます。

GCCがデフォルトの丸めモードおよび動作を想定しないようにするために、実験的なフラグ-frounding-mathをGCCに渡すことができます。

結論

ClangとGCCは、現在でも-O3IEEE-754に準拠しています。これは、IEEE-754標準の上記の規則を守る必要があることを意味します。x+0.0あるビット同一ではないxすべてのためにxこれらのルールの下で、しかし、x*1.0 そうなるように選択することができる:とき私たち、すなわち、

  1. xNaNの場合のペイロードを変更せずに渡すという推奨に従います。
  2. NaNの結果の符号ビットは、によって変更されません* 1.0
  3. がNaNでxない場合、商/積の間に符号ビットをXORする順序に従います。

IEEE-754-unsafe最適化を有効にするには(x+0.0) -> x、フラグ-ffast-mathをClangまたはGCCに渡す必要があります。


2
警告:シグナルNaNの場合はどうなりますか?(実際、どういうわけか理由だと思っていたのですが、どうすればいいかわからなかったので質問しました。)
user541686

6
@Mehrdad:C規格のIEEE 754への準拠を指定する(オプションの)部分であるAnnex Fは、シグナルNaNを明示的にカバーしていません。(C11 F.2.1。、最初の行:「この仕様はシグナリングNaNの動作を定義していません。」)Annex Fへの適合を宣言する実装は、シグナリングNaNで望むことを自由に実行できます。C ++標準には、IEEE 754の独自の処理がありますが、それが何であれ(よく知らない)、NaN動作のシグナリングも指定しているとは思いません。
user2357112は、2015

2
@Mehrdad:sNaNは、標準に従って未定義の動作を呼び出します(ただし、プラットフォームによって定義されている可能性があります)ため、ここでのコンパイラの押しつぶしが許可されます。
ジョシュア

1
@ user2357112:通常は使用されない計算の副作用としてエラートラップが発生する可能性があるため、多くの最適化が妨げられます。計算の結果が時々無視される場合、コンパイラーは結果が使用されるかどうかがわかるまで計算を延期するかもしれませんが、計算が重要なシグナルを生成した場合、それは悪いことになる場合があります。
スーパーキャット2015年

2
ああ、CとC ++の両方に合法的に当てはまる質問です。単一の標準を参照することで、両方の言語について正確に回答されます。これにより、CとC ++の両方でタグ付けされた質問について、言語の共通性に関する質問であっても、人々が不満を言う可能性が低くなりますか?悲しいことに、私はそうは思いません。
カイルストランド

35

x += 0.0場合NOOPではないxです-0.0。ただし、結果は使用されないため、オプティマイザーはループ全体を取り除くことができます。一般に、オプティマイザが決定を行う理由を理解するのは困難です。


2
私がいた後、私は実際にこれを掲示だけで、なぜ読みx += 0.0無操作ません、まだ私はループ全体がいずれかの方法を最適化する必要があるので、それはおそらく理由ではないと思いました。私は...私は期待していたとして、それは同じように完全に説得力はありませんが、それを購入することができます
user541686

オブジェクト指向言語が副作用を生み出す傾向があることを考えると、オプティマイザが実際の動作を変更していないことを確認するのは難しいと思います。
Robert Harvey、

であるため、その理由だろうlong long最適化が有効である(のために同じように動作し、GCC、でそれをやったダブル少なくとも)
E2-E4

2
@ringø:long longは整数型であり、IEEE754 型ではありません。
MSalters 2015年

1
はどうですかx -= 0、同じですか?
Viktor Mellgren、2015年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.