4

私は数日かけて理解しようとして本当に奇妙なエラーが発生したので、何が起こっているのかを理解するのに役立つコメントがないかどうかを確認したいと思います。

いくつかの背景。Boost1.45を使用してPython2.7.1にC++拡張機能を追加するソフトウェアプロジェクトに取り組んでいるため、すべてのコードはPythonインタープリターを介して実行されています。最近、回帰テストの1つを破ったコードに変更を加えました。この回帰テストは、おそらく数値の変動(たとえば、異なるマシン)に敏感すぎるため、修正する必要があります。ただし、この回帰は、元の回帰結果を生成したのと同じマシン/コンパイラで中断しているため、結果の違いをこの数値コードのスニペット(変更したコードとは明らかに無関係)まで追跡しました。

c[3] = 0.25 * (-3 * df[i-1] - 23 * df[i] - 13 * df[i+1] - df[i+2]
               - 12 * f[i-1] - 12 * f[i] + 20 * f[i+1] + 4 * f[i+2]);
printf("%2li %23a : %23a %23a %23a %23a : %23a %23a %23a %23a\n",i,
       c[3],
       df[i-1],df[i],df[i+1],df[i+2],f[i-1],f[i],f[i+1],f[i+2]);

いくつかの数値表を作成します。ご了承ください:

  • %a printsは、正確なASCII表現を提供します
  • 左側(lhs)はc [3]であり、rhsは他の8つの値です。
  • 以下の出力は、f、dfの境界から遠く離れたiの値に対するものです。
  • このコードは、iのループ内に存在し、それ自体が複数のレイヤーをネストしています(したがって、これを再現するための分離されたケースを提供することはできません)。

そこで、ソースツリーのクローンを作成しました。コンパイルする2つの実行可能ファイルの唯一の違いは、このテストでは実行されない余分なコードがクローンに含まれていることです。唯一の違いはコードがメモリ内に存在する場所にあるはずなので、これはメモリの問題であるに違いないと思います...とにかく、2つの実行可能ファイルを実行すると、生成されるものの違いは次のとおりです。

diff new.out old.out 
655,656c655,656
<  6  -0x1.7c2a5a75fc046p-10 :                  0x0p+0                  0x0p+0                  0x0p+0   -0x1.75eee7aa9b8ddp-7 :    0x1.304ec13281eccp-4    0x1.304ec13281eccp-4    0x1.304ec13281eccp-4    0x1.1eaea08b55205p-4
<  7   -0x1.a18f0b3a3eb8p-10 :                  0x0p+0                  0x0p+0   -0x1.75eee7aa9b8ddp-7   -0x1.a4acc49fef001p-6 :    0x1.304ec13281eccp-4    0x1.304ec13281eccp-4    0x1.1eaea08b55205p-4    0x1.9f6a9bc4559cdp-5
---
>  6  -0x1.7c2a5a75fc006p-10 :                  0x0p+0                  0x0p+0                  0x0p+0   -0x1.75eee7aa9b8ddp-7 :    0x1.304ec13281eccp-4    0x1.304ec13281eccp-4    0x1.304ec13281eccp-4    0x1.1eaea08b55205p-4
>  7  -0x1.a18f0b3a3ec5cp-10 :                  0x0p+0                  0x0p+0   -0x1.75eee7aa9b8ddp-7   -0x1.a4acc49fef001p-6 :    0x1.304ec13281eccp-4    0x1.304ec13281eccp-4    0x1.1eaea08b55205p-4    0x1.9f6a9bc4559cdp-5
<more output truncated>

c [3]の値は微妙に異なりますが、rhsの値はどれも違いがないことがわかります。つまり、どのように同一の入力が異なる出力を生み出しているのか。rhs式を単純化してみましたが、変更を加えると違いがなくなります。&c [3]を印刷すると、違いはなくなります。アクセスできる2つの異なるマシン(linux、osx)で実行している場合、違いはありません。これが私がすでに試したことです:

  • valgrind(Pythonで多数の問題が報告されましたが、私のコードには何も報告されておらず、深刻に見えるものもありません)
  • -D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_ASSERT -D_GLIBCXX_DEBUG_PEDASSERT -D_GLIBCXX_DEBUG_VERIFY(ただし何もアサートされません)
  • -fno-strict-aliasing(ただし、ブーストコードからエイリアシングコンパイル警告が表示されます)

問題のあるマシンでgcc4.1.2からgcc4.5.2に切り替えてみましたが、この特定の孤立した違いはなくなりました(ただし、リグレッションは失敗するため、別の問題であると想定します)。

問題をさらに切り分けるために私にできることはありますか?将来の参考のために、この種の問題をより迅速に分析または理解する方法はありますか?たとえば、rhsが変更されていないのにlhsが変更されているという私の説明を考えると、あなたは何を結論付けますか?

編集:問題は完全に原因でした-ffast-math

4

1 に答える 1

4

プログラムの浮動小数点データのタイプを変更できます。floatを使用する場合は、doubleに切り替えることができます。、、がdoubleの場合c、long doubleに切り替えることができます(Intelでは80ビット、Sparcでは128ビット)。4.5.2の場合、(128ビット)ソフトウェアでシミュレートされたタイプを使用することもできます。fdf_float128

浮動小数点型が長いほど、丸め誤差は小さくなります。

コードを追加すると(実行されていない場合でも)、結果が変わるのはなぜですか?コードサイズが変更された場合、gccはプログラムを異なる方法でコンパイルする可能性があります。GCC内には多くのヒューリスティックがあり、一部のヒューリスティックは関数サイズに基づいています。したがって、gccは関数を別の方法でコンパイルする場合があります。

-mfpmath=sse -msse2また、 x87(古いgccのデフォルトのfpmath)の使用はhttp://gcc.gnu.org/wiki/x87noteであるため、フラグを使用してプロジェクトをコンパイルしてみてください。

デフォルトでは、x87演算は真ではありません64/32ビットIEEE

PS:-ffast-math安定した数値結果に関心がある場合は、-likeオプションを使用しないでください:http://gcc.gnu.org/onlinedocs/gcc-4.1.1/gcc/Optimize-Options.html

-ffast-math -fno-math-errno、-funsafe-math-optimizations、-fno-trapping-math、-ffinite-math-only、-fno-rounding-math、-fno-signaling-nans、およびfcx-limited-rangeを設定します。

このオプションにより、プリプロセッサマクロFAST_MATHが定義されます。

このオプションは、数学関数のIEEEまたはISOルール/仕様の正確な実装に依存するプログラムの出力が正しくなくなる可能性があるため、-Oオプションでオンにしないでください。

高速計算のこの部分は結果を変える可能性があります

-funsafe-math-optimizations(a)引数と結果が有効であると想定し、(b)IEEEまたはANSI規格に違反する可能性がある浮動小数点演算の 最適化を許可します。リンク時に使用すると、デフォルトのFPU制御ワードまたは他の同様の最適化を変更するライブラリまたはスタートアップファイルが含まれる場合があります。

この部分は、トラップとNaNのようなエラーをユーザーから隠します(ユーザーがコードをデバッグするためにすべてのトラップを正確に取得したい場合があります)

-fno-trapping-math浮動小数点演算がユーザーに表示されるトラップを生成できないと想定して コードをコンパイルします。これらのトラップには、ゼロ除算、オーバーフロー、アンダーフロー、不正確な結果、および無効な操作が含まれます。このオプションは、-fno-signaling-nansを意味します。たとえば、「ノンストップ」IEEE演算に依存している場合、このオプションを設定すると、コードが高速化される可能性があります。

高速計算のこの部分では、コンパイラーはどこでもデフォルトの丸めモードを想定できると述べています(一部のプログラムではfalseになる可能性があります)。

-fno-rounding-math デフォルトの浮動小数点の丸め動作を想定した変換と最適化を有効にします。これは、すべての浮動小数点から整数への変換ではゼロに丸められ、他のすべての算術切り捨てでは最も近い値に丸められます。...このオプションを使用すると、コンパイル時に浮動小数点式を一定に折りたたむことができ(丸めモードの影響を受ける可能性があります)、符号に依存する丸めモードが存在する場合は安全ではない算術変換が可能になります。

于 2011-07-21T06:18:27.090 に答える