OpenCL浮点精度

时间:2012-06-24 11:17:12

标签: c++ windows opencl precision

我发现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,但得到了同样的错误。

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

2 个答案:

答案 0 :(得分:9)

由于在将项目从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]推入x87 FPU堆栈
  • data[i] * data[i]使用FMUL DWORD PTR data[i]
  • 计算
  • result[i]使用FLD DWORD PTR result[i]
  • 推送到x87 FPU堆栈
  • 使用FUCOMPP
  • 比较两个值

这就是问题所在。 data[i] * data[i]以80位精度驻留在x87 FPU堆栈元素中。 result[i]以32位精度来自GPU。这两个数字很可能会有所不同,因为data[i] * data[i]有更多有效数字,而result[i]有很多零(精度为80位)!

在64位模式下,事情以另一种方式发生。编译器知道你的CPU是否支持SSE,它使用SSE指令进行数学运算。在x64上以下列方式执行相同的比较语句:

  • data[i]使用MOVSS XMM0, DWORD PTR data[i]
  • 加载到SSE寄存器中
  • data[i] * data[i]使用MULSS XMM0, DWORD PTR data[i]
  • 计算
  • result[i]使用MOVSS XMM1, DWORD PTR result[i]
  • 加载到另一个SSE寄存器中
  • 使用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位模式更改浮点模型(项目属性 - &gt;配置属性 - &gt; C / C ++ - &gt;代码生成 - &gt;浮点模型)但这样做没有任何改变,因为至少在VS2010上,中间结果仍保留在FPU中。您可以做的是强制存储和重新加载计算的方形,以便在与GPU的结果进行比较之前将其舍入到32位精度。在上面的简单示例中,这可以通过更改:

来实现
if (mysqr(f) != f*f) i++;

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

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

0 of 1000000 squares differ

答案 1 :(得分:-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++;
  }

代替。