web-dev-qa-db-ja.com

.NETで浮動小数点を強制的に決定論的にする?

私は.NETの浮動小数点決定論について多くのことを読んでいます。つまり、同じ入力を持つ同じコードが異なるマシン間で同じ結果をもたらすことを保証します。 .NETにはJavaのfpstrictやMSVCのfp:strictなどのオプションがないため、 コンセンサスはそうです 純粋なマネージコードを使用してこの問題を回避する方法はありません。 C#ゲームのAI Warsは、代わりに 固定小数点演算 を使用することに決めましたが、これは面倒な解決策です。

主な問題は、CLRにより、中間結果が型のネイティブ精度よりも高い精度のFPUレジスタに存在できるようになり、予想外に高い精度の結果が得られることです。 CLRエンジニアのDavid NotarioによるMSDNの記事 は次のことを説明しています。

現在の仕様では、「予測可能性」を与えることは依然として言語の選択であることに注意してください。 言語は、すべてのFP操作の後にconv.r4またはconv.r8命令を挿入して、「予測可能な」動作を取得する場合があります。明らかに、これは非常に高価であり、言語ごとに妥協点が異なります。たとえば、C#は何もしません。絞り込みが必要な場合は、(フロート)キャストと(ダブル)キャストを手動で挿入する必要があります。

これは、浮動小数点と評価されるすべての式と部分式に明示的なキャストを挿入するだけで、浮動小数点の決定論を実現できることを示しています。このタスクを自動化するために、floatの周りにラッパータイプを書くかもしれません。これはシンプルで理想的なソリューションです!

しかし、他のコメントは、それがそれほど単純ではないことを示唆しています。 Eric Lippertが最近述べた (私の強調):

ランタイムの一部のバージョンでは、floatにキャストすると、明示的にキャストしない場合とは異なる結果が得られます。明示的にfloatにキャストすると、C#コンパイラはランタイムにヒントを与え、「もし起こったら、これを超高精度モードから外してくださいこの最適化を使用している」。

ランタイムに対するこの「ヒント」とは何ですか? C#仕様では、floatへの明示的なキャストにより、ILにconv.r4が挿入されると規定されていますか? CLR仕様では、conv.r4命令によって値がネイティブサイズに絞り込まれることが規定されていますか? David Notarioが説明しているように、これらの両方が当てはまる場合にのみ、明示的なキャストを使用して浮動小数点の「予測可能性」を提供できます。

最後に、実際にすべての中間結果をタイプのネイティブサイズに強制変換できる場合でも、これはマシン間の再現性を保証するのに十分ですか、それともFPU/SSEランタイム設定などの他の要因がありますか?

47
Asik

8087浮動小数点ユニットのチップ設計は、Intelの10億ドルの間違いでした。このアイデアは紙の上では見栄えがよく、拡張精度の80ビットで値を格納する8レジスタスタックを提供します。これにより、中間値が有効数字を失う可能性が低い計算を記述できます。

しかし、獣は最適化することは不可能です。 FPUスタックからメモリに値を保存するのはコストがかかります。したがって、それらをFPU内に保持することは、強力な最適化の目標です。必然的に、8つのレジスタしかない場合、計算が十分に深い場合はライトバックが必要になります。また、スタックとして実装されており、自由にアドレス指定できるレジスタではないため、書き戻しが発生する可能性のある体操も必要になります。必然的に、ライトバックはtruncate 80ビットから64ビットへの値になり、精度が失われます。

したがって、結果として、最適化されていないコードは、最適化されたコードと同じ結果を生成しません。また、計算に小さな変更を加えると、中間値を書き戻す必要が生じたときに、結果に大きな影響を与える可能性があります。/fp:strictオプションはその周りのハックであり、値の一貫性を維持するためにコードジェネレーターにライトバックを発行させますが、パフォーマンスの必然的かつかなりの損失を伴います。

これは完全な岩であり、難しい場所です。 x86ジッターの場合、彼らは問題に対処しようとしませんでした。

Intelは、SSE命令セットを設計したときに同じ間違いを犯しませんでした。XMMレジスタは自由にアドレス指定でき、余分なビットを格納しません。一貫した結果が必要な場合は、AnyCPUターゲットでコンパイルします。 、および64ビットオペレーティングシステムが迅速な解決策です。x64ジッタは、浮動小数点演算にFPU命令の代わりにSSEを使用します。ただし、これにより、計算で生成できる3番目の方法が追加されました。異なる結果。重要な桁数が多すぎるために計算が間違っている場合、それは一貫して間違っています。これは実際には少し臭いですが、通常はプログラマーが見る限りです。

24
Hans Passant

ランタイムに対するこの「ヒント」とは何ですか?

ご想像のとおり、コンパイラは、doubleまたはfloatへの変換が実際にソースコードに存在するかどうかを追跡し、存在する場合は、常に適切なconvオペコードを挿入します。

C#仕様では、floatへの明示的なキャストにより、ILにconv.r4が挿入されると規定されていますか?

いいえ。ただし、コンパイラのテストケースには、それを保証する単体テストがあることを保証します。仕様はそれを要求していませんが、あなたはこの振る舞いに頼ることができます。

仕様の唯一のコメントは、浮動小数点演算はランタイムの気まぐれで必要とされるよりも高い精度で実行される可能性があり、これにより、結果は予想外により正確になります。セクション4.1.6を参照してください。

CLR仕様では、conv.r4命令によって値がネイティブサイズに絞り込まれることが規定されていますか?

はい、パーティションIのセクション12.1.3で、私が注意します自分で調べた可能性がありますインターネットに依頼するのではなく。これらの仕様はWeb上で無料です。

あなたが尋ねなかったが、おそらく持っているべき質問:

フロートを高精度モードから切り捨てるキャスト以外の操作はありますか?

はい。静的フィールド、インスタンスフィールド、またはdouble[]またはfloat[]配列の要素への割り当ては切り捨てられます。

一貫した切り捨ては、マシン間の再現性を保証するのに十分ですか?

いいえ。非正規化数とNaNについては非常に興味深いセクション12.1.3を読むことをお勧めします。

そして最後に、あなたが尋ねなかったがおそらく持っているべき別の質問:

再現性のある演算を保証するにはどうすればよいですか?

整数を使用します。

27
Eric Lippert