Answers:
我使用以下代码测试了x*x*...
vs 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
请注意,我会累积每个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
这是在运行Ubuntu 9.10 64位的Intel Core Duo上。使用带有-o2优化的gcc 4.4.1进行编译。
因此,在C中,yes x*x*x
将比更快pow(x, 3)
,因为没有pow(double, int)
重载。在C ++中,大致相同。(假设我的测试方法正确)。
这是对An Markm的评论的回应:
即使using namespace std
发出了指令,如果to的第二个参数pow
是an int
,那么std::pow(double, int)
重载from <cmath>
也会被调用,而不是::pow(double, double)
from <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;
}
std::pow
,除非您使用,否则实际上会调用8次循环(对于指数> 2)-fno-math-errno
。然后,它可以像我想的那样将pow调用拉出循环。我想既然errno将是一个全球性的,线程安全的需要,它叫战俘到可能设置errno多次... EXP = 1和EXP = 2是快,因为战俘电话与刚刚吊出循环-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两倍std::pow(double base, int exponent)
,却看不到统计上有意义的性能差异。
我也想知道性能问题,并希望基于@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 :: pow的时间长40倍左右) (x,2)情况)。在所有情况下,手动扩展的效果都更好-特别是对于Power 3情况(速度提高了60倍)。如果在紧密循环中以大于2的整数幂运行std :: pow(),绝对值得牢记...
如果指数恒定且较小,则将其展开,以最大程度减少乘法次数。(例如,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)
执行我提到的优化,由此操作的顺序与指数的位模式有关。尽管如此,我不保证编译器会为您展开循环,因此仍然值得这样做手动)。
[编辑] BTW pow
在分析器结果上出现了一个(令人惊讶的)趋势。如果您不是绝对需要它(即指数很大或不是一个常数),并且您完全在乎性能,那么最好写出最佳代码并等待探查器告诉您它是(令人惊讶的) )浪费时间再进行进一步思考。(另一种方法是调用pow
并让探查器告诉您这是(毫无疑问)在浪费时间-您可以通过明智地执行此步骤来消除此步骤。)
x
整数还是浮点数?