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

时间:2016-01-11 10:02:30

标签: c++ visual-studio-2015 c++-amp

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

concurrency::fast_math::tanh运行时,

1000函数在参数大于parallel_for_each时返回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? 为什么,只在运行parrallel_for_each时返回NaN? 请告诉我原因和问题的解决方案。

2 个答案:

答案 0 :(得分:2)

fast_math中定义的函数优先考虑速度超过精度。实现和精度取决于硬件。当您不使用parallel_for_each语法时,代码将在CPU上运行,该代码仅实现一个&#34;精确的&#34; 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);
}

它可能会或可能不会比精确版本运行得更快,具体取决于硬件。所以你需要进行一些测试。

答案 1 :(得分:0)

concurrency::fast_math中的大多数功能都不能保证返回正确的值。其中一些(如tanh)甚至可以返回NaN值。在我的HD 6870上,所有数字超过90的快速tanh返回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);
}

这不会导致任何精度损失,因为float只有7位精度,而difference between Tanh(10) and 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近似值。它非常快速且相当精确。

但是,如果您需要非常准确,可以将concurrency::fast_math替换为concurrency::precise_math。但是这个选项有一个很大的缺点:precise_math无法在许多GPU上运行(例如我的6870)。 来自here

  

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

此外,precise_math可能比fast_math慢10倍以上,特别是在非专业视频卡上。
如果你不在parallel_for_each块中运行并发代码,看起来你实际上并没有使用gpu。所以,你评估在CPU上评估没有GPU特定的错误。实际上,如果你运行这段代码

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块的结果需要它。另外,对我而言,它返回的结果略有不同,但这可能取决于硬件。