【问题标题】:OpenCL Floating point precisionOpenCL 浮点精度
【发布时间】:2023-03-31 18:25:03
【问题描述】:

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

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

【问题讨论】:

标签: c++ windows opencl precision


【解决方案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++;
  }

改为。

【讨论】:

  • 我认为这不是 OP 所要求的。
【解决方案2】:

由于当您将项目从 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] 计算
  • 使用FLD DWORD PTR result[i]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上的执行方式如下:

  • 使用MOVSS XMM0, DWORD PTR data[i]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 位模式下更改浮点模型(项目属性 -> 配置属性 -> 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

【讨论】:

  • 可能有点吹毛求疵,但 x86-64 要求 CPU 具有 SSE2,而不仅仅是 SSE。
猜你喜欢
  • 2012-06-25
  • 2013-07-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-06
  • 2017-11-30
  • 2019-11-10
  • 1970-01-01
相关资源
最近更新 更多