web-dev-qa-db-ja.com

なぜClangはx + 1.0ではなくx * 1.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++の両方としてタグ付けするのは、答えがそれぞれ異なるかどうかを知りたいからです。)

125
Mehrdad

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と同じ符号を保持します。

加算の場合

デフォルトの丸めモードの下で(Round-to-Nearest、Ties-to-Even)、we x+0.0xを生成することを参照してください。ただし、x-0.0の場合を除きます。この追加により+0.0が生成される3つのルール。

+0.0は元の-0.0bitwise同一ではなく、-0.0は入力として発生する正当な値であるため、コンパイラーは潜在的な負のゼロを+0.0に変換するコードを挿入する必要があります。

要約:xの場合、デフォルトの丸めモードでは、x+0.0

  • not-0.0の場合、x自体が許容可能な出力値です。
  • is-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*1.0の指数と仮数(符号ではないが)が、入力NaNから変更されない推奨であることを意味します。上記の6.3p1に従って符号は指定されていませんが、実装はソースNaNと同一であると指定する場合があります。

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

減算の場合

デフォルトの丸めモードでは、x-0.0x + (-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の符号が加数のnoneと異なるため、+0.0は、この節に違反して、加数のtwoと符号が異なります。
  • +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に対するx+0.0およびx*1.0の正しく丸められた結果はxとまったく同じ大きさであるため、それらの指数は同じ。

sNaN

シグナリング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 [〜#〜] は、-O3であっても、IEEE-754に準拠したままです。これは、IEEE-754標準の上記の規則を守らなければならないことを意味します。 x+0.0は、これらのルールの下のすべてのxに対してxとビットが同一ではないが、x*1.0はそうなるように選択されるかもしれません:つまり、

  1. NaNの場合、xのペイロードを変更せずに渡すことをお勧めします。
  2. NaN結果の符号ビットは、* 1.0だけ変更されないままにします。
  3. xnotNaNの場合、商/製品中のXOR符号ビット)の順序に従います。

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

xx += 0.0 の場合、-0.0はNOOPではありません。ただし、結果は使用されないため、オプティマイザーはループ全体を削除できます。一般的に、オプティマイザーが決定を行う理由を判断するのは困難です。

35
user2357112