什么更有效?使用pow来平方或只是将它与自身相乘?

时间:2010-05-30 21:17:53

标签: c++ c optimization

这两种方法在C中的效率更高?怎么样:

pow(x,3)

VS。

x*x*x // etc?

7 个答案:

答案 0 :(得分:75)

我使用以下代码测试了小x*x*...的{​​{1}}与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";
}

请注意,我会累积每个pow计算的结果,以确保编译器不会对其进行优化。

如果我使用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

这是在运行Ubuntu 9.10 64bit的Intel Core Duo上。使用gcc 4.4.1编译并使用-o2优化。

因此,在C中,是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 将比x*x*x更快,因为没有pow(x, 3)重载。在C ++中,它将大致相同。 (假设我的测试方法是正确的。)


这是对An Markm的评论的回应:

即使发出了pow(double, int)指令,如果using namespace std的第二个参数是pow,那么int的{​​{1}}重载也会被调用而不是来自std::pow(double, int)的{​​{1}}。

此测试代码确认了该行为:

<cmath>

答案 1 :(得分:31)

这是一个错误的问题。正确的问题是:“对于我的代码的人类读者,哪一个更容易理解?”

如果速度很重要(稍后),请不要问,但要测量。 (在此之前,衡量一下优化它是否会产生明显的差异。)在此之前,编写代码以便最容易阅读。

<强> 修改
只是为了明确这一点(虽然它应该已经存在):突破性加速通常来自 使用更好的算法 改善数据的位置< / em> 减少使用动态内存 预先计算结果 等。 他们很少来自微优化单一功能调用 ,他们这样做,他们在 极少数地方 strong>,只能通过 小心 (且耗时) 性能分析 找到,通常比从不他们可以通过做非常直观的事情来加速(比如插入noop语句),对一个平台的优化有时候对另一个平台的优化(这就是为什么你需要衡量,而不是要求,因为我们不完全了解您的环境。

让我再次强调这一点:即使在这些事情很重要的少数几个应用程序中,它们在大多数地方都无关紧要,并且 非常你不太可能通过查看代码找到重要的地方。您确实需要 首先确定热点 ,因为否则优化代码就是 浪费时间

即使单个操作(如计算某个值的平方)占用应用程序执行时间 如果优化它可以节省该操作所需的 50%的时间 (IME甚至更为罕见),您仍然可以使应用程序 时间减少5% 您的用户需要一个秒表才能注意到这一点。 (我想在大多数情况下,大多数用户都不会注意到加速率低于20%的任何内容。 是你需要找到的四个这样的点。)

答案 2 :(得分:15)

x*xx*x*x将比pow快,因为pow必须处理一般情况,而x*x则具体。此外,您可以忽略函数调用等。

但是,如果您发现自己是这样的微优化,则需要获取一个分析器并进行一些严格的分析。压倒性的可能性是你永远不会注意到两者之间的任何差异。

答案 3 :(得分:5)

我也想知道性能问题,并希望编译器根据@EmileCormier的答案对其进行优化。但是,我担心他展示的测试代码仍然允许编译器优化掉std :: pow()调用,因为每次调用都使用相同的值,这将允许编译器存储结果和在循环中重复使用它 - 这可以解释所有情况下几乎相同的运行时间。所以我也看了一下。

这是我使用的代码(test_pow.cpp):

#include <iostream>                                                                                                                                                                                                                       
#include <cmath>
#include <chrono>

class Timer {
  public:
    explicit Timer () : from (std::chrono::high_resolution_clock::now()) { }

    void start () {
      from = std::chrono::high_resolution_clock::now();
    }

    double elapsed() const {
      return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - from).count() * 1.0e-6;
    }

  private:
    std::chrono::high_resolution_clock::time_point from;
};

int main (int argc, char* argv[])
{
  double total;
  Timer timer;



  total = 0.0;
  timer.start();
  for (double i = 0.0; i < 1.0; i += 1e-8)
    total += std::pow (i,2);
  std::cout << "std::pow(i,2): " << timer.elapsed() << "s (result = " << total << ")\n";

  total = 0.0;
  timer.start();
  for (double i = 0.0; i < 1.0; i += 1e-8)
    total += i*i;
  std::cout << "i*i: " << timer.elapsed() << "s (result = " << total << ")\n";

  std::cout << "\n";

  total = 0.0;
  timer.start();
  for (double i = 0.0; i < 1.0; i += 1e-8)
    total += std::pow (i,3);
  std::cout << "std::pow(i,3): " << timer.elapsed() << "s (result = " << total << ")\n";

  total = 0.0;
  timer.start();
  for (double i = 0.0; i < 1.0; i += 1e-8)
    total += i*i*i;
  std::cout << "i*i*i: " << timer.elapsed() << "s (result = " << total << ")\n";


  return 0;
}

这是使用:

编译的
g++ -std=c++11 [-O2] test_pow.cpp -o test_pow

基本上,区别在于std :: pow()的参数是循环计数器。正如我所担心的那样,性能上的差异是显而易见的。没有-O2标志,我的系统(Arch Linux 64位,g ++ 4.9.1,Intel i7-4930)上的结果是:

std::pow(i,2): 0.001105s (result = 3.33333e+07)
i*i: 0.000352s (result = 3.33333e+07)

std::pow(i,3): 0.006034s (result = 2.5e+07)
i*i*i: 0.000328s (result = 2.5e+07)

通过优化,结果同样引人注目:

std::pow(i,2): 0.000155s (result = 3.33333e+07)
i*i: 0.000106s (result = 3.33333e+07)

std::pow(i,3): 0.006066s (result = 2.5e+07)
i*i*i: 9.7e-05s (result = 2.5e+07)

所以看起来编译器至少尝试优化std :: pow(x,2)情况,但不是std :: pow(x,3)情况(它比std需要大约40倍) :: pow(x,2)case)。在所有情况下,手动扩展表现更好 - 但特别是对于电源3情况(快60倍)。如果在紧密循环中运行整数幂大于2的std :: pow(),这绝对值得记住......

答案 4 :(得分:4)

最有效的方法是考虑乘法的指数增长。检查此代码是否为p ^ q:

template <typename T>
T expt(T p, unsigned q){
    T r =1;
    while (q != 0) {
        if (q % 2 == 1) {    // if q is odd
            r *= p;
            q--;
        }
        p *= p;
        q /= 2;
    }
    return r;
}

答案 5 :(得分:2)

如果指数是常数且小,则将其展开,最小化乘法次数。 (例如,x^4不是最佳x*x*x*x,而是y*y y=x*x。而x^5y*y*x其中y=x*x。等等。)对于常数整数指数,只需写出优化形式;对于小指数,这是一个标准优化,无论代码是否已被分析,都应该执行。优化的形式将在如此大比例的案例中更快,基本上总是值得做。

(如果使用Visual C ++,std::pow(float,int)执行我提到的优化,其中操作序列与指数的位模式有关。我不保证编译器会为你展开循环但是,所以它仍然值得手工完成。)

[编辑] BTW pow有一个(非)令人惊讶的倾向,即在分析器结果上出现。如果你不是绝对需要它(即,指数很大或不是常数),并且你完全关心性能,那么最好写出最佳代码并等待分析器告诉你它(令人惊讶的是在进一步思考之前浪费时间。 (另一种方法是调用pow并让分析器告诉你(不出所料)浪费时间 - 你通过聪明地做这件事来削减这一步。)

答案 6 :(得分:0)

我一直在忙于类似的问题,结果令我感到困惑。我正在计算n体情况(位于距离矢量d的另一个质量为M的物体的加速度)下的牛顿引力的x /³/²:a = M G d*(d²)⁻³/²(其中d²是……的点(标量)积) d本身),我认为计算M*G*pow(d2, -1.5)M*G/d2/sqrt(d2)

更简单

诀窍在于,对于小型系统而言确实如此,但是随着系统规模的扩大,M*G/d2/sqrt(d2)变得更加高效,我不明白为什么系统规模会影响此结果,因为在不同的数据没有。好像随着系统的发展可能进行了优化,但是对于pow

是不可能的

enter image description here