У меня есть числовой код, который решает уравнение 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, что достаточно хорошо. Из того, что я нашел, накладные расходы минимальны, и это решает проблему для меня.
Очевидный обходной путь заключается в том, чтобы отметить, что в реальных 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 — если вы ищете исправление, а не обходной путь, вам нужно нанять эксперта по математике с плавающей запятой с опытом работы с открытым исходным кодом.
64-битный Linux?
Используйте код pow () из FreeBSD.
Библиотека Linux C (glibc) имеет ужасную производительность в худшем случае для определенных входных данных.
Это тоже может быть ваш алгоритм. Возможно, переключение на что-то вроде BFGS вместо метода Ньютона поможет.
Вы ничего не говорите о своих критериях конвергенции. Может быть, те тоже нуждаются в адаптации.