concurrency::fast_math::tanh() возвращает NaN в parallel_for_each (C++ AMP)

Я рассчитал значение с помощью C++ amp. Среда: VS2015, Win8.
При запуске функции parallel_for_each значение было NaN. Причиной была функция concurrency::fast_math::tanh.

Функция concurrency::fast_math::tanh возвращает NaN, когда аргумент больше 1000 при прогоне через parallel_for_each:

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? Почему возвращает NaN только при запуске parallel_for_each ? Подскажите пожалуйста причину и решение проблемы.


person Riyaaaaa    schedule 11.01.2016    source источник
comment
извините, tanh перенастраивает NaN, если t › 100 тоже.   -  person Riyaaaaa    schedule 11.01.2016
comment
(e^x+e^-x)/(e^x-e^-x) с большим x я мог получить плохие результаты.   -  person Yakk - Adam Nevraumont    schedule 11.01.2016


Ответы (2)


Функции, определенные в fast_math, отдают предпочтение скорости, а не точности. Реализация и точность зависят от аппаратного обеспечения. Если вы не используете синтаксис parallel_for_each, код будет выполняться на ЦП, который реализует только одну «точную» функцию 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);
}

Он может работать быстрее, а может и не работать быстрее, чем конкретная версия, в зависимости от аппаратного обеспечения. Итак, вам нужно провести несколько тестов.

person Ari Hietanen    schedule 11.01.2016

Большинство функций в concurrency::fast_math не гарантируют возврат правильного значения. Некоторые из них (например, tanh) могут даже возвращать значения NaN. На моем HD 6870 быстрое число всех чисел больше 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::fast_math на concurrency::precise_math. Но у этого варианта есть большой недостаток: precise_math не может работать на многих графических процессорах (например, на моем 6870). Из здесь.

Эти функции, в том числе функции с одинарной точностью, требуют расширенной поддержки двойной точности в ускорителе. Вы можете использовать элемент данных accelerator::supports_double_precision, чтобы определить, можете ли вы запускать эти функции на конкретном ускорителе.

Кроме того, precise_math может быть более чем в 10 раз медленнее, чем fast_math, особенно на непрофессиональных видеокартах.

Если вы запускаете параллельный код не в блоке parallel_for_each, похоже, что вы на самом деле не используете 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 это требуется. Кроме того, для меня он возвращает немного другие результаты, но это может зависеть от оборудования.

person Drobor    schedule 16.01.2016