Какой из этих двух методов в C более эффективен? А как насчет:
pow(x,3)
против
x*x*x // etc?
c++
c
optimization
jamylak
источник
источник
x
неотъемлемой или с плавающей точкой?Ответы:
Я проверил разницу в производительности между
x*x*...
vspow(x,i)
for small,i
используя этот код:Результаты:
Обратите внимание, что я накапливаю результат каждого вычисления мощности, чтобы убедиться, что компилятор не оптимизирует его.
Если я использую
std::pow(double, double)
версию иloops = 1000000l
, я получаю:Это на Intel Core Duo под управлением Ubuntu 9.10 64bit. Скомпилировано с использованием gcc 4.4.1 с оптимизацией -o2.
Так что в C да
x*x*x
будет быстрееpow(x, 3)
, потому что нетpow(double, int)
перегрузки. В C ++ будет примерно так же. (Предполагая, что методология моего тестирования верна.)Это ответ на комментарий An Markm:
Даже если
using namespace std
была выпущена директива, если вторым параметром дляpow
является значениеint
, тогда будет вызыватьсяstd::pow(double, int)
перегрузка from<cmath>
вместо::pow(double, double)
from<math.h>
.Этот тестовый код подтверждает такое поведение:
источник
std::pow
8 * циклов (для экспоненты> 2), если вы не используете-fno-math-errno
. Затем он может вытащить вызов pow из цикла, как я и думал. Я предполагаю, что поскольку errno является глобальным, потокобезопасность требует, чтобы он вызывал pow, чтобы, возможно, устанавливать errno несколько раз ... exp = 1 и exp = 2 работают быстро, потому что вызов pow выводится из цикла с помощью только-O3
... ( с - ffast-math , он также вычисляет сумму 8 вне цикла.)pow
вызов, выведенный из цикла, поэтому здесь есть большой недостаток. Кроме того, похоже, что вы в основном тестируете задержку добавления FP, поскольку все тесты выполняются за одно и то же время. Можно было ожидать, чтоtest5
это будет медленнееtest1
, но это не так. Использование нескольких аккумуляторов разделит цепочку зависимостей и скроет задержку.pow
к постоянно меняющемуся значению (чтобы предотвратить повторное выражение pow).Это неправильный вопрос. Правильный вопрос: «Какой из них легче понять людям, читающим мой код?»
Если скорость имеет значение (позже), не спрашивайте, а измеряйте. (А перед этим измерьте, действительно ли оптимизация будет иметь какое-либо заметное значение.) А пока пишите код так, чтобы его было легче читать.
Редактировать
Просточтобы сделать это ясно (хотя это уже должно было): Прорывные ускорения обычно исходят извещейкак , используя лучшие алгоритмы , улучшение локальности данных , сокращение использования динамической памяти , предварительно вычисление результатов и т.д. Они редко приходят из микро-оптимизирующие вызовы отдельных функций , а там, где они есть, они делают это в очень немногих местах , которые могут быть обнаружены только путем тщательного (и трудоемкого) профилирования , чаще, чем никогда, их можно ускорить, выполняя очень неинтуитивно понятные вещи (например, вставка
noop
операторов), а то, что является оптимизацией для одной платформы, иногда является пессимизацией для другой (вот почему вам нужно измерять, а не спрашивать, потому что мы не полностью знаем / не имеем вашей среды).Позвольте мне подчеркнуть это еще раз: Даже в тех немногих случаях , когда такие вещи важны, они не имеют значения в большинстве мест они используются, и это очень маловероятно , что вы найдете места , где они имеют значение, глядя на код. Вам действительно нужно сначала определить горячие точки , потому что в противном случае оптимизация кода будет пустой тратой времени .
Даже если одна операция (например, вычисление квадрата некоторого значения) занимает 10% времени выполнения приложения (какой IME встречается довольно редко), и даже при оптимизации она экономит 50% времени, необходимого для этой операции (какой IME является даже гораздо реже), вы все равно заставили приложение работать всего на 5% меньше времени .
Вашим пользователям понадобится секундомер, чтобы даже это заметить. (Думаю, в большинстве случаев ускорение ниже 20% остается незамеченным для большинства пользователей. И это четыре таких места, которые вам нужно найти.)
источник
x*x
илиx*x*x
будет быстрееpow
, так какpow
должен иметь дело с общим случаем, тогдаx*x
как конкретным. Также вы можете опустить вызов функции и тому подобное.Однако, если вы обнаружите, что подобным образом оптимизируете микро-оптимизацию, вам нужно получить профилировщик и провести серьезное профилирование. Вполне вероятно, что вы никогда не заметите никакой разницы между ними.
источник
x*x*x
vs doublestd::pow(double base, int exponent)
во временном цикле и не увидел статистически значимой разницы в производительности.Мне также было интересно узнать о проблеме с производительностью, и я надеялся, что это будет оптимизировано компилятором на основе ответа от @EmileCormier. Однако меня беспокоило, что тестовый код, который он показал, по-прежнему позволит компилятору оптимизировать вызов std :: pow (), поскольку в вызове каждый раз использовались одни и те же значения, что позволило бы компилятору сохранять результаты и повторно использовать его в цикле - это объяснило бы почти одинаковое время выполнения для всех случаев. Так что я тоже посмотрел на это.
Вот код, который я использовал (test_pow.cpp):
Это было скомпилировано с использованием:
По сути, разница в том, что аргумент std :: pow () - это счетчик цикла. Как я и опасался, разница в производительности явная. Без флага -O2 результаты в моей системе (64-разрядная версия Arch Linux, g ++ 4.9.1, Intel i7-4930) были:
С оптимизацией результаты были одинаково поразительными:
Итак, похоже, что компилятор хотя бы пытается оптимизировать случай std :: pow (x, 2), но не случай std :: pow (x, 3) (это занимает примерно в 40 раз больше времени, чем std :: pow (x, 2) случай). Во всех случаях ручное расширение выполнялось лучше, но особенно для случая power 3 (в 60 раз быстрее). Это определенно стоит иметь в виду, если запускать std :: pow () с целыми степенями больше 2 в жестком цикле ...
источник
Самый эффективный способ - это рассмотреть экспоненциальный рост умножения. Проверьте этот код для p ^ q:
источник
Если показатель постоянный и маленький, увеличьте его, минимизируя количество умножений. (Например,
x^4
не оптимальноx*x*x*x
, аy*y
гдеy=x*x
. Аx^5
естьy*y*x
гдеy=x*x
. И так далее.) Для постоянных целочисленных показателей просто выпишите уже оптимизированную форму; с небольшими показателями это стандартная оптимизация, которая должна выполняться независимо от того, профилирован код или нет. Оптимизированная форма будет работать быстрее в таком большом проценте случаев, что это всегда стоит делать.(Если вы используете Visual C ++,
std::pow(float,int)
выполняет оптимизацию, на которую я ссылаюсь, в результате чего последовательность операций связана с битовым шаблоном экспоненты. Я не гарантирую, что компилятор развернет цикл за вас, тем не менее, так что это все равно стоит сделать это вручную.)[править] Кстати,
pow
у профилировщика есть (не) удивительная тенденция появляться в результатах профилировщика. Если он вам абсолютно не нужен (т. Е. Показатель большой или непостоянный), и вас вообще беспокоит производительность, тогда лучше всего написать оптимальный код и подождать, пока профилировщик вам это скажет (что удивительно ) зря трачу время, прежде чем думать дальше. (Альтернативный вариант - позвонитьpow
и попросить профилировщика сказать вам, что (что неудивительно) зря теряет время - вы сокращаете этот шаг, выполняя его с умом.)источник
Я был занят аналогичной проблемой и очень озадачен результатами. Я вычислял x⁻³ / ² для ньютоновской гравитации в ситуации с n телами (ускорение, полученное от другого тела массы M, расположенного на расстоянии вектора d):
a = M G d*(d²)⁻³/²
(где d² - точечное (скалярное) произведение d самого по себе), и я подумал, что вычислитьM*G*pow(d2, -1.5)
будет проще, чемM*G/d2/sqrt(d2)
Хитрость в том, что это верно для небольших систем, но по мере роста системы она
M*G/d2/sqrt(d2)
становится более эффективной, и я не понимаю, почему размер системы влияет на этот результат, потому что повторение операции с другими данными не влияет. Это как если бы были возможные оптимизации по мере роста системы, но которые невозможны сpow
источник