プロジェクトをx86からx64に切り替えても、GPUコードは何も変わらないため、CPUでの乗算の実行方法と同じようにすべてを行う必要があります。x86モードとx64モードでの浮動小数点数の処理には微妙な違いがあります。最大の違いは、x64 CPUはSSEとSSE2もサポートしているため、Windowsの64ビットモードでの数学演算にデフォルトで使用されることです。
HD4770 GPUは、単精度浮動小数点ユニットを使用してすべての計算を実行します。一方、最新のx64 CPUには、浮動小数点数を処理する2種類の機能ユニットがあります。
- 80ビットのはるかに高い拡張精度で動作するx87FPU
- 32ビットおよび64ビットの精度で動作し、他のCPUが浮動小数点数を処理する方法と多くの互換性があるSSEFPU
32ビットモードでは、コンパイラはSSEが使用可能であるとは想定せず、計算を行うために通常のx87FPUコードを生成します。この場合、のような操作data[i] * data[i]
は、はるかに高い80ビットの精度を使用して内部で実行されます。種類の比較はif (results[i] == data[i] * data[i])
次のように実行されます。
data[i]
を使用してx87FPUスタックにプッシュされますFLD DWORD PTR data[i]
data[i] * data[i]
を使用して計算されますFMUL DWORD PTR data[i]
result[i]
を使用してx87FPUスタックにプッシュされますFLD DWORD PTR result[i]
- 両方の値はを使用して比較されます
FUCOMPP
ここに問題があります。data[i] * data[i]
80ビット精度のx87FPUスタック要素に存在します。result[i]
32ビット精度のGPUから来ています。data[i] * data[i]
有効桁数がはるかに多いのに対しresult[i]
、ゼロがたくさんある(80ビット精度)ため、両方の数値が異なる可能性があります。
64ビットモードでは、別の方法で処理が行われます。コンパイラは、CPUがSSE対応であることを認識しており、SSE命令を使用して計算を行います。同じ比較ステートメントがx64で次のように実行されます。
data[i]
を使用してSSEレジスタにロードされますMOVSS XMM0, DWORD PTR data[i]
data[i] * data[i]
を使用して計算されますMULSS XMM0, DWORD PTR data[i]
result[i]
を使用して別のSSEレジスタにロードされますMOVSS XMM1, DWORD PTR result[i]
- 両方の値はを使用して比較されます
UCOMISS XMM1, XMM0
この場合、二乗演算は、GPUで使用されるのと同じ32ビット単精度で実行されます。80ビット精度の中間結果は生成されません。そのため、結果は同じです。
GPUが関与していなくても、これを実際にテストするのは非常に簡単です。次の簡単なプログラムを実行するだけです。
#include <stdlib.h>
#include <stdio.h>
float mysqr(float f)
{
f *= f;
return f;
}
int main (void)
{
int i, n;
float f, f2;
srand(1);
for (i = n = 0; n < 1000000; n++)
{
f = rand()/(float)RAND_MAX;
if (mysqr(f) != f*f) i++;
}
printf("%d of %d squares differ\n", i);
return 0;
}
mysqr
中間の80ビットの結果が32ビットの精度で変換されるように特別に記述されていますfloat
。コンパイルして64ビットモードで実行すると、出力は次のようになります。
0 of 1000000 squares differ
コンパイルして32ビットモードで実行すると、出力は次のようになります。
999845 of 1000000 squares differ
原則として、32ビットモードで浮動小数点モデルを変更できるはずです(プロジェクトプロパティ->構成プロパティ-> C / C ++->コード生成->浮動小数点モデル)が、少なくともVS2010中間では、変更しても何も変更されません。結果は引き続きFPUに保持されます。実行できることは、計算された正方形のストアとリロードを強制して、GPUからの結果と比較する前に32ビットの精度に丸められるようにすることです。上記の簡単な例では、これは次のように変更することで実現されます。
if (mysqr(f) != f*f) i++;
に
if (mysqr(f) != (float)(f*f)) i++;
変更後、32ビットコードの出力は次のようになります。
0 of 1000000 squares differ