Очень медленный std :: pow () для баз, очень близких к 1

У меня есть числовой код, который решает уравнение f(x) = 0в котором я должен поднять x к власти p, Я решаю это, используя кучу вещей, но в конце концов у меня есть метод Ньютона. Решение оказывается равным x = 1и, следовательно, является причиной моих проблем. Когда итеративное решение приближается к 1, сказать x = 1 + 1e-13время, необходимое для вычисления std::pow(x, p) растет чрезвычайно легко, в 100 раз, делая мой код непригодным для использования.

Эта машина работает под управлением AMD64 (Opteron 6172) на CentOS, и команда проста y = std::pow(x, p);, Подобное поведение проявляется на всех моих машинах, на всех x64. Как задокументировано Вот, это не только моя проблема (то есть, кто-то еще пьяный), появляется только на x64 и только для x рядом с 1.0, Подобная вещь случается с exp,

Решение этой проблемы жизненно важно для меня. Кто-нибудь знает, есть ли способ обойти эту медлительность?

РЕДАКТИРОВАТЬ: Джон указал, что это связано с ненормальными. Вопрос в том, как это исправить? Код C ++, скомпилированный с g++ для использования в GNU Octave, Похоже, что хотя я установил CXXFLAGS включать -mtune=native а также -ffast-math, это не помогает, и код работает так же медленно.

PSEUDO-SOLUTION FOR NOW: Для всех, кому небезразлична эта проблема, предложенные ниже решения не сработали лично для меня. Мне действительно нужна обычная скорость std::pow(), но без вялости вокруг x = 1, Решение для меня лично заключается в использовании следующего хака:

inline double mpow(double x, double p) __attribute__ ((const));

inline double mpow(double x, double p)
{
double y(x - 1.0);
return (std::abs(y) > 1e-4) ? (std::pow(x, p)) : (1.0 + p * y * (1.0 + (p - 1.0) * y * (0.5 + (1.0 / 6.0) * (p - 2.0) * y)));
}

Граница может быть изменена, но для -40 < п < 40 ошибка меньше, чем примерно 1e-11, что достаточно хорошо. Из того, что я нашел, накладные расходы минимальны, и это решает проблему для меня.

5

Решение

Очевидный обходной путь заключается в том, чтобы отметить, что в реальных a ** b == exp(log(a) * b) и используйте эту форму вместо. Вам нужно будет убедиться, что это не повлияет на точность ваших результатов. Изменить: как обсуждалось, это также страдает от замедления до почти такой же степени.

Проблема не в ненормальностях, по крайней мере, не напрямую; пытаясь вычислить exp(-2.4980018054066093e-15) страдает такое же замедление, и -2.4980018054066093e-15, конечно, не ненормальный.

Если вас не интересует точность ваших результатов, то масштабирование экспоненты или экспоненты должно вывести вас за пределы медленной зоны:

sqrt(pow(a, b * 2))
pow(a * 2, b) / pow(2, b)
...

Эта ошибка известна сопровождающим glibc: http://sourceware.org/bugzilla/show_bug.cgi?id=13932 — если вы ищете исправление, а не обходной путь, вам нужно нанять эксперта по математике с плавающей запятой с опытом работы с открытым исходным кодом.

9

Другие решения

64-битный Linux?

Используйте код pow () из FreeBSD.

Библиотека Linux C (glibc) имеет ужасную производительность в худшем случае для определенных входных данных.

Увидеть: http://entropymine.com/imageworsener/slowpow/

1

Это тоже может быть ваш алгоритм. Возможно, переключение на что-то вроде BFGS вместо метода Ньютона поможет.

Вы ничего не говорите о своих критериях конвергенции. Может быть, те тоже нуждаются в адаптации.

0
По вопросам рекламы [email protected]