Emi*_*ier 78

我使用此代码测试了x*x*...vs pow(x,i)for small 之间的性能差异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";
}
Run Code Online (Sandbox Code Playgroud)

结果是:

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
Run Code Online (Sandbox Code Playgroud)

请注意,我累积每个pow计算的结果,以确保编译器不会优化它.

如果我使用该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
Run Code Online (Sandbox Code Playgroud)

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

所以在C中,是的x*x*x会更快pow(x, 3),因为没有pow(double, int)过载.在C++中,它将大致相同.(假设我的测试方法是正确的.)


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

即使using namespace std发出了指令,如果第二个参数pow为a int,那么将调用std::pow(double, int)重载<cmath>而不是::pow(double, double)来自<math.h>.

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

#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;
}
Run Code Online (Sandbox Code Playgroud)

  • 我在[godbolt](https://goo.gl/ibtNL8)上进行了尝试(注释掉了计时,因为Godbolt没有安装Boost。出乎意料的是,除非您使用`-fno-math-errno`,否则它实际上会调用`std :: pow` 8次循环(对于指数&gt; 2)。然后它可以像我想的那样将pow调用拉出循环。我猜因为errno是全局的,所以线程安全性要求它调用pow可能多次设置errno ... exp = 1和exp = 2很快,因为pow调用仅通过-O3吊出循环。 ([[-ffast-math](https://goo.gl/Helef1),它也在循环外执行8之和。) (2认同)

sbi*_*sbi 30

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

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

编辑
只是为了明确这一点(虽然它应该已经存在):突破性的加速通常来自于使用更好的算法,改善数据的局部性,减少动态内存的使用,预先计算结果等等.它们很少来自微优化单个函数调用,并且在他们这样做的情况下,他们在很少的地方这样做,这只能通过仔细(和耗时)的分析找到,更多的时候通过非常不直观的方式来加速事情(比如插入noop 对一个平台的优化有时是对另一个平台的优化(这就是为什么你需要衡量,而不是要求,因为我们不完全了解/拥有你的环境).

让我再次强调这一点:即使在这样的事情重要的几个应用程序,他们没有在他们使用的最重要的地方,而这是非常不可能的,你会发现,他们通过查看代码问题的地方.你确实需要首先确定热点,否则优化代码只是浪费时间.

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

  • Stackoverflow应该有一个插入标准免责声明的按钮:"我已经知道过早优化是邪恶的,但我要求这个优化问题用于学术目的,或者我已经将该行/代码块识别为瓶颈". (132认同)
  • 这可能是一个正确的问题.也许他没有考虑自己的实际项目,只是对语言/编译器如何工作感兴趣...... (42认同)
  • 我不认为可读性是一个问题.写x*x与pow(x,2)似乎都很清楚. (39认同)
  • 过度使用粗体和斜体,不容易在眼睛上. (39认同)
  • 我不完全同意这个答案.询问表现是一个有效的问题.您可以实现的最佳性能有时是有效的要求,通常是某人使用c ++而不是其他语言的原因.测量并不总是一个好主意.我可以测量冒泡排序和快速排序,并使用我的10个项目更快地找到bubblesort因为我没有背景知道项目的数量非常重要并且稍后我的1,000,000个项目找到它是一个非常糟糕的选择. (22认同)
  • +1用于测量.我刚刚测试了两种方法之间的性能,并惊讶地发现没有明显的区别.学术上的"推理"太多了.:-) (5认同)
  • 这种态度为什么软件不断变得更加缓慢而硬件不断变得更强大? (5认同)
  • @sbi你有没有看过我关于FMUL的陈述?除非你可以构造一个汇编语句,其中pow(x,3)占用的循环少于两个FMUL或一个不会将x*x*x映射到两个FMUL的编译器,它代表.现在有可能,有些编译器可能会将pow(x,3)映射到两个FMUL操作,但我们知道至少有一个编译器没有.x86和IA-64架构没有方形,立方体或电源指令.按理说x*x*x与pow(x,3)相同或更快.它被称为推理,它比基准测试更强大. (3认同)
  • 我唯一不喜欢这种通用的非答案的事实是,我几乎在每个与优化相关的问题上都看到了它.我们在这里是因为我们很好奇,不是因为我们希望通过一些居高临下的笨蛋第一百万次传播过早优化.如果您甚至不想尝试回答这个问题,那么您至少可以将回复留作评论而不是答案. (3认同)
  • @Emile:如果评论中有一个类似的样板按钮,那么我会支持这样的样板按钮"你在询问之前测试了吗?". (2认同)
  • 这正是要问的问题.请求有经验的开发人员的知识可以揭示关于编译器优化/展开,宏,C/C++标准等的大量见解.OP以及大多数观众已经知道"什么更容易阅读"的答案".它们都很容易阅读.数学运算符,如幂函数,在科学编程中被大量使用,并且对于具有大N的循环将占用大量的CPU时间.这是编程中的标准情况. (2认同)
  • 我认为应该是"甚至"一些'有经验的开发人员不知道'.你应该在阅读最佳答案后知道.为了优化,最好使用x*x*x代替pow(x,3)因为我们不能确保pow(x,3)内联为pow(float,int).两个FMUL操作将比JMP更少的周期以及Pow中实现的其他方法.真正的问题是编译器是否优化了pow回答两个FMUL操作.(FWIW,我已经用多种语言编程了20年,其中很大一部分时间用于科学编程.) (2认同)

Pup*_*ppy 16

x*x或者x*x*x会比自己更快pow,因为pow必须处理一般情况,而x*x具体而言.此外,您可以忽略函数调用等.

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

  • 在我决定测试它之前,我一直在想同样的事情.我只是在一个定时循环中测试了`x*x*x` vs double`std :: pow(double base,int exponent)`,并且看不到具有统计意义的性能差异. (6认同)
  • 确保它没有被编译器优化掉. (2认同)
  • 您不能假设通用函数速度较慢。有时情况恰恰相反,因为更简单的代码更容易让编译器优化。 (2认同)

jdt*_*ier 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;
}
Run Code Online (Sandbox Code Playgroud)

这是使用以下编译的:

g++ -std=c++11 [-O2] test_pow.cpp -o test_pow
Run Code Online (Sandbox Code Playgroud)

基本上,不同之处在于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)
Run Code Online (Sandbox Code Playgroud)

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

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)
Run Code Online (Sandbox Code Playgroud)

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


mha*_*hat 5

最有效的方法是考虑乘法的指数增长。检查 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;
}
Run Code Online (Sandbox Code Playgroud)