OpenCL浮点精度

我在OpenCL中发现了host-client float标准的一个问题。 问题是在x86中编译时,由Opencl计算的浮点数与我的visual studio 2010编译器不在同一个浮点数限制内。 但是,当在x64编译它们是在相同的限制。 我知道它必须是, http://www.viva64.com/en/b/0074/

我在testing过程中使用的源代码是: http : //www.codeproject.com/Articles/110685/Part-1-OpenCL-Portable-Parallelism当我在x86中运行程序时,它会给我202个相等的数字,内核和C ++程序占用了1269760个数字的平方。 然而在64位构build中,1269760个数字是正确的,换句话说是100%。 此外,我发现opencl和x86 c ++的计算结果之间的误差是5.5385384e-014,这个数字是一个非常小的部分,但是不足够小,这个数字的小数部分是2.92212543378266922312416e-19。
这是因为,错误需要比epsilon小,以便程序可以将这两个数字识别为一个单一的相等数字。 当然,通常情况下,人们永远不会比较本地的花车,但是很高兴知道浮动限制是不同的。 是的,我试图设置flt:静态,但得到了同样的错误。

所以我想对这种行为进行一些解释。 在此先感谢所有的答案。

由于在将项目从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])的执行如下:

  • 使用FLD DWORD PTR data[i]data[i]压入x87 FPU堆栈,
  • data[i] * data[i]使用FMUL DWORD PTR data[i]
  • result[i]使用FLD DWORD PTR result[i]被压入x87 FPU堆栈FLD DWORD PTR result[i]
  • 两个值都使用FUCOMPP进行比较

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

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

  • 使用MOVSS XMM0, DWORD PTR data[i] data[i]加载到SSE寄存器中MOVSS XMM0, DWORD PTR data[i]
  • data[i] * data[i]使用MULSS XMM0, DWORD PTR data[i]
  • result[i]使用MOVSS XMM1, DWORD PTR 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中。 你可以做的是强制存储和重新加载计算的平方,以便它将被四舍五入到32位精度之前,它是从GPU的结果进行比较。 在上面的简单例子中,这是通过改变来实现的:

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

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

更改后的32位代码输出变成:

 0 of 1000000 squares differ 

在我的情况

 (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++; } 

代替。