[解決済み] なぜClangはx * 1.0を最適化し、x + 0.0を最適化しないのですか?
質問
なぜ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++の両方としてタグ付けしています)
どのように解決するのですか?
浮動小数点演算の規格である IEEE 754-2008 と ISO/IEC 10967 Language Independent Arithmetic (LIA) 規格、第 1 部 がその理由を回答しています。
IEEE 754 §6.3 符号ビット
入力または結果のいずれかがNaNのとき、本標準はNaNの符号を解釈しない。しかし、ビット文字列の操作(copy、negate、abs、copySign)は、NaNオペランドの符号ビットに基づいて、NaN結果の符号ビットを指定することがあることに注意してください。論理述語totalOrderも、NaNオペランドの符号ビットに影響される。他のすべての操作について、この規格は、1つの入力NaNがある場合、またはNaNが無効な操作から生成される場合であっても、NaN結果の符号ビットを指定しない。
入力も結果もNaNでないとき、積または商の符号はオペランドの符号の排他的論理和であり、和、または和x+(-y)とみなされる差x-yの符号は、最大でも加算器の符号の1つと異なり、和の符号は加算器の符号の1つと異なる。 和の符号又は和x+(-y)とみなされる差x-yの符号は,加算符号のうちの最大1つと異なる;及び 変換,量子化演算,roundToIntegral演算及びroundToIntegralExact (5.3.1 参照 ) の結果の符号は,最初の又は唯一のオペランドの符号とする。これらの規則は,オペランド又は結果がゼロ又は無限である場合にも適用されるものとする。
反対の符号をもつ二つのオペランドの和(又は同符号をもつ二つのオペランドの差)が正確にゼロであるとき,その和(又は差)の符号は,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.0
である場合、もし
x
-
は
-0.0
であればx
自体が許容される出力値です。 -
は
-0.0
であれば、出力値 でなければなりません。+0.0
とはビット的に同じではありません。-0.0
.
乗算の場合
デフォルトの丸めモードでは
であれば、そのような問題は発生しません。
x*1.0
. もし
x
:
-
は(副)正常数です。
x*1.0 == x
を常に使用します。 -
は
+/- infinity
であれば、結果は+/- infinity
という同じ符号の -
は
NaN
であれば、以下に従ってIEEE 754 §6.2.3 NaN伝搬
NaN オペランドをその結果に伝搬し、入力として単一の NaN を持つ演算は、宛先形式で表現可能であれば、入力 NaN のペイロードを持つ NaN を生成する必要があります。
の指数と仮数(符号は違うが)は、NaNになることを意味します。
NaN*1.0
は 推奨 は、入力から変更されないようにNaN
. 符号は上記§6.3p1に従って不特定ですが、実装では、ソースNaN
. -
は
+/- 0.0
であれば、結果は0
の符号ビットと XOR し、その符号ビットが1.0
の符号ビットとXORされています。の符号ビットは1.0
は0
であれば、出力値は入力から変化しない。このようにx*1.0 == x
のときでもx
が (負の) ゼロであってもです。
引き算の場合
デフォルトの丸めモードでは
の場合、減算は
x-0.0
と等価であるため、これもダメです。
x + (-0.0)
. もし
x
は
-
は
NaN
であれば、§6.3p1と§6.2.3は加算と乗算とほぼ同じように適用されます。 -
は
+/- 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
とは符号が異なる。 に と異なり、この条項に違反します。 -
は
+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.0
と
x*1.0
に対して、有限の
x
と全く同じ大きさです。
x
と同じ大きさであり、その指数も同じです。
sNaNs
Signaling NaNs は浮動小数点トラップ値で、浮動小数点オペランドとして使用すると無効な演算例外 (SIGFPE) が発生する特別な NaN 値です。例外をトリガーするループが最適化された場合、ソフトウェアの動作はもはや同じではありません。
しかし、user2357112 のように
がコメントで指摘しているように
のように、C11 標準は NaN のシグナリングの挙動を明示的に未定義のままにしています (
sNaN
) の動作は未定義であるため、コンパイラーは NaN が発生しないと仮定してよく、したがって NaN が発生する例外も発生しないと仮定してよいことになっています。C++11 標準では、NaN をシグナリングする動作の記述が省略されているため、これも未定義のままになっています。
丸めモード
代替丸めモードでは、許容される最適化が変更される場合があります。例えば
負の無限大に丸める
モードでは、最適化
x+0.0 -> x
が許されるようになりますが
x-0.0 -> x
は禁じ手となります。
GCCがデフォルトの丸めモードと振る舞いを仮定するのを防ぐために、実験的なフラグである
-frounding-math
を GCC に渡すことができます。
結論
Clangと
GCC
であっても
-O3
であっても、IEEE-754に準拠したままです。これは、IEEE-754 標準の上記の規則を守らなければならないことを意味します。
x+0.0
は
ビット同一でない
に対して
x
に対して、すべての
x
というルールで、しかし
x*1.0
が選ばれるかもしれません。
: すなわち、私たちが
-
のペイロードを変更せずに渡すという勧告に従いましょう。
x
がNaNである場合、ペイロードを変更せずに渡すという勧告に従うこと。 -
NaN の結果の符号ビットを変更しないようにするには
* 1.0
. -
以下の場合、商/積の間に符号ビットを XOR するという命令に従います。
x
は ではない はNaNです。
IEEE-754-unsafeの最適化を有効にするために
(x+0.0) -> x
は、フラグ
-ffast-math
を Clang や GCC に渡す必要があります。
関連
-
[解決済み】ENOENTが「そのようなファイルやディレクトリはありません」という意味であるのはなぜですか?
-
[解決済み】getline()が何らかの入力の後に使用されると動作しない 【重複あり
-
[解決済み] to_string は std のメンバーではない、と g++ が言っている (mingw)
-
[解決済み] なぜGCCはa*a*a*a*aを(a*a*a)*(a*a*a)に最適化しないのでしょうか?
-
[解決済み] 0.1fを0にすると、なぜ10倍もパフォーマンスが落ちるのですか?
-
[解決済み] 通貨を表すのにDoubleやFloatを使ってはいけないのですか?
-
[解決済み] Cプリプロセッサはなぜ "linux "という単語を定数 "1 "と解釈するのですか?
-
[解決済み] Collatz予想の検証を行うC++のコードは、なぜ手書きのアセンブリよりも高速に動作するのでしょうか?
-
[解決済み] Math.round(0.4999999999994) はなぜ1を返すのですか?
-
[解決済み] なぜsizeof(x++)はxをインクリメントしないのですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] テスト
-
[解決済み】識別子 "string "は未定義?
-
[解決済み】致命的なエラー LNK1169: ゲームプログラミングで1つ以上の多重定義されたシンボルが発見された
-
[解決済み】C++ 式はポインタからオブジェクトへの型を持っている必要があります。
-
[解決済み】変数 '' を抽象型 '' と宣言できない。
-
[解決済み] error: 'if' の前に unqualified-id を期待した。
-
[解決済み】「corrupted size vs. prev_size」glibc エラーを理解する。
-
[解決済み] 非常に基本的なC++プログラムの問題 - バイナリ式への無効なオペランド
-
[解決済み】オブジェクト引数のない非静的メンバ関数の呼び出し コンパイラーエラー
-
[解決済み】エラー。引数リストに一致するコンストラクタのインスタンスがない