这两种方法在C中的效率更高?怎么样:
pow(x,3)
VS。
x*x*x // etc?
答案 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*x
或x*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^5
是y*y*x
其中y=x*x
。等等。)对于常数整数指数,只需写出优化形式;对于小指数,这是一个标准优化,无论代码是否已被分析,都应该执行。优化的形式将在如此大比例的案例中更快,基本上总是值得做。
(如果使用Visual C ++,std::pow(float,int)
执行我提到的优化,其中操作序列与指数的位模式有关。我不保证编译器会为你展开循环但是,所以它仍然值得手工完成。)
[编辑] BTW pow
有一个(非)令人惊讶的倾向,即在分析器结果上出现。如果你不是绝对需要它(即,指数很大或不是常数),并且你完全关心性能,那么最好写出最佳代码并等待分析器告诉你它(令人惊讶的是在进一步思考之前浪费时间。 (另一种方法是调用pow
并让分析器告诉你(不出所料)浪费时间 - 你通过聪明地做这件事来削减这一步。)
答案 6 :(得分:0)