何年も前、私はMicrosoft Xbox 360部門で働いていました。 新しいコンソールをリリースすることを考えていたので、このコンソールが前世代のコンソールからゲームを実行できるといいと思いました。
エミュレーションは常に困難ですが、企業の上司が中央処理装置のタイプを絶えず変更している場合、さらに困難になることが判明しています。 最初のXbox(Xbox Oneと混同しないでください)はx86 CPUを使用しました。 2番目のXboxでは、申し訳ありませんが、Xbox 360はPowerPCプロセッサを使用していました。 3番目のXbox、つまりXbox Oneは、x86 / x64 CPUを使用しました。 異なるISA間のこのような飛躍は、私たちの生活を単純化しませんでした。
私はXbox 360に最初のXboxの多くのゲームをエミュレートすること、つまりPowerPCでx86をエミュレートすることを教えたチームの作業に参加しました。 次に、x64 CPUでXbox 360 PowerPC CPUをエミュレートする問題を調査するように依頼されました。 前もって申し上げますが、満足できる解決策は見つかりませんでした。
FMA!= MMA
私を悩ませたものの1つは、融合乗算加算( FMA命令)でした。 これらの命令は、入力で3つのパラメーターを受け取り、最初の2つを乗算し、3番目のパラメーターを追加しました。 Fusedは、操作の終了まで丸めが実行されなかったことを意味します。 つまり、乗算は完全な精度で実行され、その後加算が実行されてから、結果が最終回答に丸められます。
これを具体的な例で示すために、10進数の浮動小数点数と2桁の精度の数字を使用することを想像してみましょう。 関数として示されるこの計算を想像してください:
FMA(8.1e1, 2.9e1, 4.1e1), 8.1e1 * 2.9e1 + 4.1e1, 81 * 29 + 41
81*29
は
2349
等しく、41を追加すると
2390
ます。 2桁に
2.4e3
と、
2400
または
2.4e3
得られます。
FMAがない場合、まず乗算を実行し、
2349
取得する必要があります。これにより、2桁の精度に切り上げられ、
2300 (2.3e3)
得られます。 次に、
41
を追加して
2341
を取得します。これは再び丸められ、最終結果
2300 (2.3e3)
を取得します。これは、FMAの回答よりも精度が低くなります。
注1:FMA(a,b, -a*b)
はa*b
誤差を計算しますが、これは実際にはクールです。
注2:注1の副作用の1つは、コンピューターがFMA命令を自動的に生成する場合、x = a * b – a * b
はゼロを返さない場合があることです。
したがって、明らかに、FMAは乗算命令と加算命令を別々に行うよりも正確な結果を提供します。 深くはしませんが、2つの数値を乗算してから3番目の数値を追加する必要がある場合、FMAはその代替よりも正確になることに同意します。 さらに、FMA命令は、乗算命令とそれに続く加算命令よりも遅延が少ないことがよくあります。 Xbox 360 CPUでは、レイテンシとFMA処理速度はfmulまたはfaddと同等であったため、 fmulの代わりにFMAを使用し、その後に依存するfaddを使用すると、遅延を半分に減らすことができました。
FMAエミュレーション
Xbox 360コンパイラーは 、ベクトルとスカラーの両方のFMA命令 を常に生成しています。 選択したx64プロセッサがこれらの命令をサポートするかどうか確信が持てなかったため、それらを迅速かつ正確にエミュレートすることが重要でした。 これらの命令のエミュレーションが理想的であることが必要でした。浮動小数点計算をエミュレートする以前の経験から、「かなり近い」結果がキャラクターを床から落としたり、車が世界を飛び出したりすることを知っていたからです。
x64 CPUがFMA命令をサポートしていない場合、FMA命令を完全にエミュレートするには何が必要ですか?
幸いなことに、ゲームの浮動小数点計算の大部分は浮動小数点精度(32ビット)で実行され、FMAエミュレーションでは倍精度(64ビット)の命令を喜んで使用できました。
倍精度の計算を使用して、浮動小数点精度でFMA命令をエミュレートするのは簡単なはずです( ナレーターの声:しかしそうではありません 。 Floatの精度は24ビットで、doubleの精度は53ビットです。 これは、入力フロートを倍精度に変換する(ロスレス変換)場合、エラーなしで乗算を実行できることを意味します。 つまり、完全に正確な結果を保存するには、48ビットの精度で十分であり、さらに多く、つまりすべてが正常に機能しています。
次に、追加を行う必要があります。 float形式の2番目の項を取得し、doubleに変換してから、乗算の結果に追加するだけで十分です。 丸めは乗算のプロセスでは発生せず、加算後にのみ実行されるため、FMAをエミュレートするにはこれで十分です。 私たちのロジックは完璧です。 勝利を宣言して家に帰ることができます。
勝利はとても近かった...
しかし、それは機能しません。 または、少なくとも一部の着信データで失敗します。 なぜこれが起こるのか考えてみてください。
保留音が鳴る...
FMAの定義により、乗算と加算が完全な精度で実行され、その後、結果が精度浮動小数点数で丸められるため、エラーが発生します。 ほぼ達成できました。
乗算は丸めなしで行われ、その後、加算後に丸めが実行されます。 これは、私たちがやろうとしていることに似ています。 ただし、加算後の丸めは倍精度で行われます。 その後、結果を浮動小数点精度で保存する必要があります。これが、丸めが再び発生する理由です。
プーさん ダブル丸め 。
これを明確に示すのは難しいので、単精度が小数点以下2桁、倍精度が4桁の10進浮動小数点形式に戻りましょう。 そして、
FMA(8.1e1, 2.9e1, 9.9e-1)
または
81 * 29 + .99
を計算すると想像してみましょう。
この式に対する正確な答えは、
2349.99
または
2.34999e3
です。 精度を1桁(2桁)に丸めると、
2.3e3
得られ
2.3e3
。 これらの計算をエミュレートしようとしたときに何がうまくいかないか見てみましょう。
倍精度で
81
と
29
を乗算すると、
2349
得られます。 これまでのところ良い。
次に
.99
を追加して
2349.99
を取得し
2349.99
。 すべて順調です。
この結果はdoubleの精度に丸められ、
2350 (2.350e3)
ます。 おっと
精度を単精度に丸め 、IEEEの規則に従って最も近い値に丸めて
2400 (2.4e3)
を取得します。 これは間違った答えです。 FMA命令によって返される正しく丸められた結果よりもわずかに大きなエラーがあります。
問題は、最も近い偶数までIEEE環境ルールにあると述べることができます。 ただし、選択する丸めルールに関係なく、二重丸めが真のFMAとは異なる結果を返す場合が常にあります。
どうしてそれはすべて終わりましたか?
この問題に対する完全に満足できる解決策を見つけることができませんでした。
Xbox Oneのリリースよりもずっと前にXboxチームを辞め、それ以来コンソールにあまり注意を払っていないので、彼らがどのような決定を下したのかわかりません。 最新のx64 CPUには、このような操作を完全にエミュレートできるFMA命令があります。 また、x87数学コプロセッサーを使用してFMAをエミュレートすることもできます。この質問を研究したときにどのような結論に至ったか覚えていません。 または、おそらく開発者は結果がかなり近く、使用できると判断しただけです。