concurrency::fast_math::tanh()在parallel_for_each(C++AMP)处返回N

concurrency::fast_math::tanh() returns NaN at the parallel_for_each (C++ AMP)

本文关键字:C++AMP each 返回 for parallel tanh fast concurrency math      更新时间:2023-10-16

我用c++amp计算了这个值。环境:VS2015,Win8.
当运行parallel_for_each函数时,值为NaN。原因是concurrency::fast_math::tanh功能。

当参数在parallel_for_each:中运行时大于1000时,concurrency::fast_math::tanh函数返回NaN

float arr[2];
concurrency::array_view<float> arr_view(2, arr);
concurrency::extent<1> ex;
ex[0] = 1;
parallel_for_each(ex, [=](Concurrency::index<1> idx) restrict(amp){
    float t = 10000000;
    arr_view[0] = concurrency::fast_math::fabs(t);
    arr_view[1] = concurrency::fast_math::tanh(t);
});
arr_view.synchronize();
std::cout << arr[0] << "," << arr[1] << std::endl;

输出

1e+07,nan

case2,如果未运行parallel_for_each:

float arr[2];
concurrency::array_view<float> arr_view(2, arr);
concurrency::extent<1> ex;
ex[0] = 1;
float t = 10000000;
arr_view[0] = concurrency::fast_math::fabs(t);
arr_view[1] = concurrency::fast_math::tanh(t);
arr_view.synchronize();
std::cout << arr[0] << "," << arr[1] << std::endl;

输出:

1e+07,1

这是我一直期待的结果。如果将tanh更改为tanhf,则结果相同。

为什么tanh函数返回NaN?为什么,只在运行paralle_for_each时返回NaN?请告诉我原因和问题的解决方法。

fast_math中定义的函数将速度置于精度之上。实现和精度取决于硬件。当你不使用parallel_for_each语法时,代码将在CPU上运行,CPU只实现一个"精确"的tanh函数,因此给出了正确的答案。

要解决此问题,您可以调用precise_math、下的函数

concurrency::precise_math::tanh(t);

如果这太慢,并且fast_math::tanh的精度足够,你可以尝试类似的方法

double myTanh(double t){
  return (concurrency::fast_math::fabs(t)>100) ? concurrency::precise_math::copysign(1,t) : concurrency::fast_math::tanh(t);
}

根据硬件的不同,它可能比精确版本运行得更快,也可能不会。所以你需要运行一些测试。

concurrency::fast_math中的大多数函数都不能保证返回正确的值。其中一些(如tanh)甚至可以返回NaN值。在我的HD 6870所有数字的快速tanh超过90返回NaN
以下是解决这个问题的一些技巧。

你可以将Tanh的论点"绑定"到10

float Tanh(float val) restrict(amp)
{
    if (val > 10)
        return 1;
    else if (val < -10)
        return-1;
    return Concurrency::fast_math::tanh(val);
}

这不会造成任何精度损失,因为浮点只有7位数的精度,而Tanh(10)和1之间的差是4*10-9

或者,您可以实现自己的Tanh函数,它不会有这样的限制

float Tanh(float val) restrict(amp)
{
    float ax = fabs(val);
    float x2 = val * val;
    float z = val * (1.0f + ax + (1.05622909486427f + 0.215166815390934f * x2 * ax) * x2);
    return (z / (1.02718982441289f + fabs(z)));
}

在很久以前的某个地方发现了这个tanh近似。它速度很快,而且相当精确

但是,如果您需要非常精确的tanh,您可以用concurrency::precise_math替换concurrency::fast_math。但这个选项有一个主要的缺点:precise_math不能在很多GPU上运行(例如我的6870)。从这里开始。

这些功能,包括单精度功能,需要扩展的双精度支持在油门上。您可以使用accelerator::supports_double_precision数据成员,以确定可以在特定的加速器上运行这些功能。

此外,precise_math可能比fast_math慢10倍以上,尤其是在非专业视频卡上

如果你运行的并发代码不在parallel_for_each块中,那么看起来你实际上并没有使用gpu。因此,tanh u评估在没有GPU特定错误的CPU上进行评估。事实上,如果你运行这个代码

float t = 0.65;
arr_view[1] = concurrency::fast_math::tanh(t);  
parallel_for_each(e, [=](index<1> idx)      restrict(amp)
{
    arr_view[0] = concurrency::fast_math::tanh(t);
}); 
std::cout << arr[0] << "," << arr[1] << std::endl;
arr_view.synchronize();
std::cout << arr[0] << "," << arr[1] << std::endl;
std::cout << arr[0] - arr[1] << std::endl;//may return non-zero value, depending on gpu

您可以在同步前看到第一个tanh的结果,同时获得parallel_for_each块需要的结果。此外,对我来说,它返回的结果略有不同,但这可能取决于硬件。