6

我发现 OpenCL 中的主机 - 客户端浮动标准存在问题。问题是在 x86 中编译时,Opencl 计算的浮点数与我的 Visual Studio 2010 编译器的浮点数限制不同。但是,在 x64 中编译时,它们的限制相同。我知道它必须与http://www.viva64.com/en/b/0074/

我在测试期间使用的源是:http: //www.codeproject.com/Articles/110685/Part-1-OpenCL-Portable-Parallelism 当我在 x86 中运行程序时,它会给我 202 个相等的数字,当内核和 C++ 程序取了 1269760 个数字的平方。然而,在 64 位版本中,1269760 个数字是正确的,即 100%。此外,我发现opencl和x86 c++的计算结果之间的误差为5.5385384e-014,与数字的epsilon 2.92212543378266922312416e-19相比,这是一个很小的分数,但还不够小。
那是因为,误差需要小于 epsilon,这样程序才能将这两个数字识别为一个相等的数字。当然,通常人们永远不会在本地比较浮点数,但很高兴知道浮点数限制是不同的。是的,我尝试设置 flt:static,但得到了同样的错误。

所以我想对这种行为做出某种解释。提前感谢所有答案。

4

2 回答 2

10

由于当您将项目从 x86 切换到 x64 时,GPU 代码没有任何变化,这一切都与如何在 CPU 上执行乘法有关。在 x86 和 x64 模式下处理浮点数之间存在一些细微差别,最大的区别在于,由于任何 x64 CPU 也支持 SSE 和 SSE2,因此它默认用于 Windows 上 64 位模式下的数学运算。

HD4770 GPU 使用单精度浮点单元进行所有计算。另一方面,现代 x64 CPU 有两种处理浮点数的功能单元:

  • x87 FPU 以更高的 80 位扩展精度运行
  • SSE FPU 以 32 位和 64 位精度运行,并且与其他 CPU 处理浮点数的方式非常兼容

在 32 位模式下,编译器不假定 SSE 可用并生成通常的 x87 FPU 代码来进行数学运算。在这种情况下,类似的操作data[i] * data[i]是在内部使用更高的 80 位精度执行的。种类的比较if (results[i] == data[i] * data[i])如下进行:

  • data[i]使用FLD DWORD PTR data[i]
  • data[i] * data[i]计算使用FMUL DWORD PTR data[i]
  • result[i]使用推入 x87 FPU 堆栈FLD DWORD PTR result[i]
  • 两个值都使用比较FUCOMPP

问题来了。data[i] * data[i]以 80 位精度驻留在 x87 FPU 堆栈元素中。result[i]来自 GPU 的 32 位精度。这两个数字很可能会有所不同,因为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 位模式下更改浮点模型(Project properties -> Configuration Properties -> C/C++ -> Code Generation -> Floating Point Model)但是这样做不会改变任何事情,因为至少在 VS2010 中间结果仍保存在 FPU 中。您可以做的是强制存储和重新加载计算平方,以便在将其与 GPU 的结果进行比较之前将其舍入到 32 位精度。在上面的简单示例中,这是通过更改:

if (mysqr(f) != f*f) i++;

if (mysqr(f) != (float)(f*f)) i++;

更改后32位代码输出变为:

0 of 1000000 squares differ
于 2012-06-26T14:09:39.653 回答
-1

就我而言

(float)(f*f)

没有帮助。我用了

  correct = 0;
  for(unsigned int i = 0; i < count; i++) {
    volatile float sqr = data[i] * data[i];
    if(results[i] == sqr)
      correct++;
  }

反而。

于 2012-09-11T22:59:49.980 回答