什么更有效率? 使用pow来平方或只是乘以它自己?
这两种方法在C中更有效率? 以及如何:
pow(x,3)
与
x*x*x // etc?
我testing了x*x*...
与pow(x,i)
之间的性能差异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位的英特尔酷睿双核。 使用gcc 4.4.1编译使用-o2优化。
所以在C中,是x*x*x
会比pow(x, 3)
更快,因为没有pow(double, int)
过载。 在C ++中,它将大致相同。 (假设我testing的方法是正确的。)
这是针对安马克所做的评论:
即使using namespace std
指令,如果pow
的第二个参数是int
,那么来自<cmath>
的std::pow(double, int)
重载将被调用,而不是::pow(double, double)
<math.h>
。
这个testing代码证实了这个行为:
#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; }
这是一个错误的问题。 正确的问题是:“我的代码的读者更容易理解哪一个?”
如果速度很重要(以后),不要问,而是测量。 (在此之前,测量是否优化这实际上会造成任何明显的差异。)在此之前,写代码,以便它是最容易阅读。
编辑
为了说明这一点(虽然它本来应该是这样):突破性的加速通常来自于使用更好的algorithm , 改善数据的局部性 , 减lessdynamic内存的使用 , 预计 算 结果等等。 它们很less来自微- 优化单个函数调用 ,以及他们在哪里执行,他们在很less的地方这样做,这只能通过仔细 (耗时)的分析来发现,而不是从来不会通过做非直观的事情来加速(比如插入noop
语句),一个平台的优化有时候是另一个平台的问题(这就是为什么你需要测量,而不是问,因为我们不完全知道/有你的环境)。
让我再次强调这一点:即使在less数应用程序中这些事情很重要,在大多数使用它们的地方并不重要,通过查看代码,您很难find他们所关注的地方。 你确实需要首先确定热点 ,否则优化代码只是浪费时间 。
即使单个操作(如计算某个值的平方)占用了应用程序执行时间的10% (该IME非常less见),即使优化该操作也可节省该操作所需时间的50% (即IME为甚至更less见),但您仍然只需要less5%的时间 。
您的用户将需要秒表甚至注意到。 (我想在大多数情况下,对于大多数用户来说,加速20%以下的东西是不会被注意到的, 这就是你需要find的四个这样的地方。)
x x或x x x将比pow更快,因为pow必须处理一般情况,而x x是特定的。 此外,你可以ellide函数调用等。
但是,如果您发现自己像这样进行微型优化,则需要获取一个分析器并进行一些严格的分析。 压倒性的概率是你永远不会注意到两者之间的任何区别。
我也想知道性能问题,并希望这将被编译器优化,基于@EmileCormier的答案。 然而,我担心他所显示的testing代码仍然允许编译器优化掉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()是循环计数器的参数。 正如我担心的那样,performance的差异是显而易见的。 如果没有-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)
所以它看起来像编译器至less试图优化std :: pow(x,2)的情况下,而不是std :: pow(x,3)的情况下(它需要约40倍的时间比std :: pow (x,2)情况)。 在所有情况下,手动扩展效果都更好 – 但是对于功率3的情况尤其如此(快60倍)。 这是绝对值得注意的是,如果在一个严格的循环中运行整数次幂大于2的std :: pow()…
最有效的方法是考虑乘法的指数增长。 检查这个代码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; }
如果指数恒定而小,则将其展开,最小化乘法的次数。 (例如, 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
并让分析器告诉你这是浪费时间(不出所料) – 你正在智能化地去掉这一步。)