Что более эффективно? Используя голову к квадрату или просто умножают его с собой?

Что из этих двух методов находится в более эффективном C? Как насчет:

pow(x,3)

по сравнению с.

x*x*x // etc?
114
задан jamylak 1 May 2013 в 13:15
поделиться

4 ответа

Я проверил разницу в производительности между x * x * ... и pow (x, i) для малых i ] с использованием этого кода:

#include <cstdlib>
#include <cmath>
#include <boost/date_time/posix_time/posix_time.hpp>

inline boost::posix_time::ptime now()
{
    return boost::posix_time::microsec_clock::local_time();
}

#define TEST(num, expression) \
double test##num(double b, long loops) \
{ \
    double x = 0.0; \
\
    boost::posix_time::ptime startTime = now(); \
    for (long i=0; i<loops; ++i) \
    { \
        x += expression; \
        x += expression; \
        x += expression; \
        x += expression; \
        x += expression; \
        x += expression; \
        x += expression; \
        x += expression; \
        x += expression; \
        x += expression; \
    } \
    boost::posix_time::time_duration elapsed = now() - startTime; \
\
    std::cout << elapsed << " "; \
\
    return x; \
}

TEST(1, b)
TEST(2, b*b)
TEST(3, b*b*b)
TEST(4, b*b*b*b)
TEST(5, b*b*b*b*b)

template <int exponent>
double testpow(double base, long loops)
{
    double x = 0.0;

    boost::posix_time::ptime startTime = now();
    for (long i=0; i<loops; ++i)
    {
        x += std::pow(base, exponent);
        x += std::pow(base, exponent);
        x += std::pow(base, exponent);
        x += std::pow(base, exponent);
        x += std::pow(base, exponent);
        x += std::pow(base, exponent);
        x += std::pow(base, exponent);
        x += std::pow(base, exponent);
        x += std::pow(base, exponent);
        x += std::pow(base, exponent);
    }
    boost::posix_time::time_duration elapsed = now() - startTime;

    std::cout << elapsed << " ";

    return x;
}

int main()
{
    using std::cout;
    long loops = 100000000l;
    double x = 0.0;
    cout << "1 ";
    x += testpow<1>(rand(), loops);
    x += test1(rand(), loops);

    cout << "\n2 ";
    x += testpow<2>(rand(), loops);
    x += test2(rand(), loops);

    cout << "\n3 ";
    x += testpow<3>(rand(), loops);
    x += test3(rand(), loops);

    cout << "\n4 ";
    x += testpow<4>(rand(), loops);
    x += test4(rand(), loops);

    cout << "\n5 ";
    x += testpow<5>(rand(), loops);
    x += test5(rand(), loops);
    cout << "\n" << x << "\n";
}

Результаты:

1 00:00:01.126008 00:00:01.128338 
2 00:00:01.125832 00:00:01.127227 
3 00:00:01.125563 00:00:01.126590 
4 00:00:01.126289 00:00:01.126086 
5 00:00:01.126570 00:00:01.125930 
2.45829e+54

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

Если я использую версию std :: pow (double, double) и loops = 1000000l , я получаю:

1 00:00:00.011339 00:00:00.011262 
2 00:00:00.011259 00:00:00.011254 
3 00:00:00.975658 00:00:00.011254 
4 00:00:00.976427 00:00:00.011254 
5 00:00:00.973029 00:00:00.011254 
2.45829e+52

Это на Intel Core Duo под управлением Ubuntu 9.10 64бит. Скомпилировано с использованием 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) из вместо :: pow (double, double) из .

Этот тестовый код подтверждает такое поведение:

#include <iostream>

namespace foo
{

    double bar(double x, int i)
    {
        std::cout << "foo::bar\n";
        return x*i;
    }


}

double bar(double x, double y)
{
    std::cout << "::bar\n";
    return x*y;
}

using namespace foo;

int main()
{
    double a = bar(1.2, 3); // Prints "foo::bar"
    std::cout << a << "\n";
    return 0;
}
78
ответ дан 24 November 2019 в 02:36
поделиться

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

2
ответ дан 24 November 2019 в 02:36
поделиться

Это неправильный вопрос. Правильный вопрос: «Какой из них легче понять людям, читающим мой код?»

Если скорость имеет значение (позже), не спрашивайте, а измеряйте. (А перед этим измерьте, действительно ли оптимизация будет иметь какое-либо заметное значение.) А пока пишите код так, чтобы его было легче читать.

Править
Чтобы прояснить это (хотя это уже должно было быть): Прорывное ускорение обычно происходит от таких вещей, как , с использованием лучших алгоритмов , улучшения локальности данных , сокращение использования динамической памяти , предварительное вычисление результатов и т. Д. Они редко происходят из микрооптимизирующих вызовов отдельных функций , а там, где они появляются, они делают это в очень немногих местах , которые могут быть обнаружены только осторожно (и отнимающее много времени) профилирование , чаще, чем никогда, их можно ускорить, выполняя очень неинтуитивные вещи (например, вставляя операторы noop ), и что является оптимизацией для одной платформы иногда является пессимизацией для другого (вот почему вам нужно измерять, а не спрашивать, потому что мы не полностью знаем / не имеем вашего окружения).

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

Даже если одна операция (например, вычисление квадрата некоторого значения) занимает 10% времени выполнения приложения (такой IME встречается довольно редко), и даже если оптимизация позволяет сэкономить 50 % времени , необходимого для этой операции (а IME даже намного, намного реже), вы все равно заставляете приложение использовать только на 5% меньше времени .
Вашим пользователям понадобится секундомер, чтобы даже это заметить. (Я полагаю, что в большинстве случаев все, что ниже 20% ускорения, остается незамеченным для большинства пользователей. И , что - это четыре таких места, которые вам нужно найти.)

30
ответ дан 24 November 2019 в 02:36
поделиться

x * x или x * x * x будет быстрее, чем pow , поскольку pow должен иметь дело с общим случаем, тогда как x * x специфичен. Также вы можете опустить вызов функции и тому подобное.

Однако, если вы обнаружите, что выполняете такую ​​микрооптимизацию, вам необходимо получить профилировщик и провести серьезное профилирование. Велика вероятность того, что вы никогда не заметите никакой разницы между ними.

16
ответ дан 24 November 2019 в 02:36
поделиться