使用pow(x,2)而不是x * x有什么优势?
使用这个代码有什么好处吗?
double x; double square = pow(x,2);
而不是这个?
double x; double square = x*x;
我更喜欢x * x,并看着我的实现(微软)我发现在pow没有优势,因为x * x比pow更简单的scheme。
有什么特别的情况下pow是优越的?
FWIW,在MacOS X 10.6和-O3
编译器标志上使用gcc-4.2,
x = x * x;
和
y = pow(y, 2);
导致相同的汇编代码:
#include <cmath> void test(double& x, double& y) { x = x * x; y = pow(y, 2); }
组装到:
pushq %rbp movq %rsp, %rbp movsd (%rdi), %xmm0 mulsd %xmm0, %xmm0 movsd %xmm0, (%rdi) movsd (%rsi), %xmm0 mulsd %xmm0, %xmm0 movsd %xmm0, (%rsi) leave ret
所以只要你使用了一个体面的编译器,写出哪个更适合你的应用程序,但是考虑pow(x, 2)
永远不会比普通的乘法更优化。
如果你的意思是x²,std :: pow更具performance力,如果你的意思是x x ,x x更具performance力 ,特别是如果你只是编码,比如科学论文,读者应该能够理解你的实现和论文。 对于x * x / x 2来说,这个区别是微妙的,但是我认为如果你使用一般的命名函数,它会增加代码效率和可读性。
在现代编译器上,比如g ++ 4.x,std :: pow(x,2)将被内联,如果它甚至不是内置的编译器,并且强度减小到x * x。 如果不是默认的,你不关心IEEE浮点types一致性,请检查你的编译器手册中的快速算术开关(g ++ == -ffast-math)。
旁注:有人提到,包括math.h增加程序的大小。 我的回答是:
在C ++中,
#include <cmath>
, 而不是math.h。 另外,如果你的编译器不是老式的,只会增加你的程序的大小(一般情况下),如果std :: pow的实现只是内联到相应的x87指令,而现代的g ++将用x * x强度减小x 2,那么没有相关的尺寸增加。 而且,程序的大小永远不会决定你如何expression你的代码。
与math.h相比,cmath的另一个优点是使用cmath,可以为每个浮点types获得一个std :: pow重载,而对于math.h,则可以在全局命名空间中获得pow,powf等,所以cmath会增加适应性的代码,特别是在编写模板时。
作为一般规则: 在可疑的基础性能和二进制大小推理代码上优先selectexpression和清晰的代码。
另见Knuth:
“我们应该忘记小效率,大约97%的时间:不成熟的优化是万恶的根源”
和jackson:
程序优化的第一条规则:不要这样做。 程序优化的第二条规则(仅适用于专家!):不要这样做。
不仅x*x
更清晰,它肯定至less和pow(x,2)
一样快。
这个问题涉及到大多数C和C ++关于科学编程实现的关键弱点之一。 在从Fortran转换到C大约二十年后,再到C ++之后,这仍然是那些偶尔让我怀疑这个转换是否是一件好事情的痛处之一。
这个问题简而言之:
- 实现
pow
的最简单方法是Type pow(Type x; Type y) {return exp(y*log(x));}
- 大多数C和C ++编译器采取简单的方法。
- 有些人可能“做正确的事情”,但只在高度优化的水平。
- 与
x*x
相比,使用pow(x,2)
的简单方法在计算上非常昂贵,并且失去了精度。
与旨在科学编程的语言相比:
- 你不写
pow(x,y)
。 这些语言有一个内置的求幂运算符。 C和C ++坚决拒绝执行一个指数运算符,使许多科学程序员程序员的血液沸腾。 对于一些死忠的Fortran程序员来说,这就是永远不会改用C的原因。 - Fortran(和其他语言)需要为所有小整数幂做“正确的事情”,其中small是介于-12和12之间的任何整数。(如果编译器不能“做正确的事” 。)而且,他们被要求这样做,优化closures。
- 许多Fortran编译器也知道如何提取一些合理的根源,而不用简单的方法。
依靠高优化级别来“做正确的事情”存在一个问题。 我曾为多个禁止在安全关键软件中使用优化的组织工作。 在这里损失了一千万美元之后,存储器可能会非常长(数十年),在那里损失1亿美元,这全部是由于一些优化编译器中的错误。
恕我直言,一个不应该在C或C ++中使用pow(x,2)
。 我并不孤单。 使用pow(x,2)
程序员通常会在代码审查期间大量使用。
在C ++ 11中,有一种情况是使用x * x
优于std::pow(x,2)
,这种情况下你需要在constexpr中使用它:
constexpr double mySqr( double x ) { return x * x ; }
我们可以看到std :: pow没有标记为constexpr ,所以它在constexpr函数中是不可用的。
否则,从性能的angular度来看,把下面的代码放到godbolt中可以看到这些函数:
#include <cmath> double mySqr( double x ) { return x * x ; } double mySqr2( double x ) { return std::pow( x, 2.0 ); }
生成相同的组件:
mySqr(double): mulsd %xmm0, %xmm0 # x, D.4289 ret mySqr2(double): mulsd %xmm0, %xmm0 # x, D.4292 ret
我们应该期望从任何现代编译器得到类似的结果。
值得注意的是,目前海湾合作委员会认为pow constexpr ,也涵盖在这里,但这是一个不符合延伸,不应该依赖,并可能会改变在以后的版本gcc
。
x * x
总是会编译成简单的乘法。 pow(x, 2)
很可能,但决不是保证,要优化到相同的。 如果没有进行优化,可能会使用缓慢的通用提升功率math程序。 所以如果你的performance是你所关心的,你应该总是喜欢x * x
。
恕我直言:
- 代码可读性
- 代码健壮性 – 将更容易更改为
pow(x, 6)
,也许某些特定处理器的浮点机制被实现等等。 - 性能 – 如果有一个更聪明,更快速的方式来计算(使用汇编或某种特殊的技巧),pow将做到这一点。 你不会.. 🙂
干杯
我可能会selectstd::pow(x, 2)
因为它可以使我的代码重构更容易。 一旦代码被优化,它就不会有任何区别。
现在,这两种方法是不一样的。 这是我的testing代码:
#include<cmath> double square_explicit(double x) { asm("### Square Explicit"); return x * x; } double square_library(double x) { asm("### Square Library"); return std::pow(x, 2); }
asm("text");
调用只是将注释写入我使用的汇编输出(OS X 10.7.4上的GCC 4.8.1):
g++ example.cpp -c -S -std=c++11 -O[0, 1, 2, or 3]
你不需要-std=c++11
,我只是经常使用它。
首先:debugging时(零优化),生产的组件不同; 这是相关部分:
# 4 "square.cpp" 1 ### Square Explicit # 0 "" 2 movq -8(%rbp), %rax movd %rax, %xmm1 mulsd -8(%rbp), %xmm1 movd %xmm1, %rax movd %rax, %xmm0 popq %rbp LCFI2: ret LFE236: .section __TEXT,__textcoal_nt,coalesced,pure_instructions .globl __ZSt3powIdiEN9__gnu_cxx11__promote_2IT_T0_NS0_9__promoteIS2_XsrSt12__is_integerIS2_E7__valueEE6__typeENS4_IS3_XsrS5_IS3_E7__valueEE6__typeEE6__typeES2_S3_ .weak_definition __ZSt3powIdiEN9__gnu_cxx11__promote_2IT_T0_NS0_9__promoteIS2_XsrSt12__is_integerIS2_E7__valueEE6__typeENS4_IS3_XsrS5_IS3_E7__valueEE6__typeEE6__typeES2_S3_ __ZSt3powIdiEN9__gnu_cxx11__promote_2IT_T0_NS0_9__promoteIS2_XsrSt12__is_integerIS2_E7__valueEE6__typeENS4_IS3_XsrS5_IS3_E7__valueEE6__typeEE6__typeES2_S3_: LFB238: pushq %rbp LCFI3: movq %rsp, %rbp LCFI4: subq $16, %rsp movsd %xmm0, -8(%rbp) movl %edi, -12(%rbp) cvtsi2sd -12(%rbp), %xmm2 movd %xmm2, %rax movq -8(%rbp), %rdx movd %rax, %xmm1 movd %rdx, %xmm0 call _pow movd %xmm0, %rax movd %rax, %xmm0 leave LCFI5: ret LFE238: .text .globl __Z14square_libraryd __Z14square_libraryd: LFB237: pushq %rbp LCFI6: movq %rsp, %rbp LCFI7: subq $16, %rsp movsd %xmm0, -8(%rbp) # 9 "square.cpp" 1 ### Square Library # 0 "" 2 movq -8(%rbp), %rax movl $2, %edi movd %rax, %xmm0 call __ZSt3powIdiEN9__gnu_cxx11__promote_2IT_T0_NS0_9__promoteIS2_XsrSt12__is_integerIS2_E7__valueEE6__typeENS4_IS3_XsrS5_IS3_E7__valueEE6__typeEE6__typeES2_S3_ movd %xmm0, %rax movd %rax, %xmm0 leave LCFI8: ret
但是,当你产生优化的代码(即使在GCC的最低优化级别,意味着-O1
),代码也是一样的:
# 4 "square.cpp" 1 ### Square Explicit # 0 "" 2 mulsd %xmm0, %xmm0 ret LFE236: .globl __Z14square_libraryd __Z14square_libraryd: LFB237: # 9 "square.cpp" 1 ### Square Library # 0 "" 2 mulsd %xmm0, %xmm0 ret
所以,除非你关心未经优化的代码的速度,否则没有什么区别。
就像我说的:在我看来, std::pow(x, 2)
更清楚地expression了你的意图,但这是一个偏好问题,而不是performance。
即使对于更复杂的expression式,优化似乎也是如此。 拿,例如:
double explicit_harder(double x) { asm("### Explicit, harder"); return x * x - std::sin(x) * std::sin(x) / (1 - std::tan(x) * std::tan(x)); } double implicit_harder(double x) { asm("### Library, harder"); return std::pow(x, 2) - std::pow(std::sin(x), 2) / (1 - std::pow(std::tan(x), 2)); }
再次,用-O1
(最低优化),程序集又是一样的:
# 14 "square.cpp" 1 ### Explicit, harder # 0 "" 2 call _sin movd %xmm0, %rbp movd %rbx, %xmm0 call _tan movd %rbx, %xmm3 mulsd %xmm3, %xmm3 movd %rbp, %xmm1 mulsd %xmm1, %xmm1 mulsd %xmm0, %xmm0 movsd LC0(%rip), %xmm2 subsd %xmm0, %xmm2 divsd %xmm2, %xmm1 subsd %xmm1, %xmm3 movapd %xmm3, %xmm0 addq $8, %rsp LCFI3: popq %rbx LCFI4: popq %rbp LCFI5: ret LFE239: .globl __Z15implicit_harderd __Z15implicit_harderd: LFB240: pushq %rbp LCFI6: pushq %rbx LCFI7: subq $8, %rsp LCFI8: movd %xmm0, %rbx # 19 "square.cpp" 1 ### Library, harder # 0 "" 2 call _sin movd %xmm0, %rbp movd %rbx, %xmm0 call _tan movd %rbx, %xmm3 mulsd %xmm3, %xmm3 movd %rbp, %xmm1 mulsd %xmm1, %xmm1 mulsd %xmm0, %xmm0 movsd LC0(%rip), %xmm2 subsd %xmm0, %xmm2 divsd %xmm2, %xmm1 subsd %xmm1, %xmm3 movapd %xmm3, %xmm0 addq $8, %rsp LCFI9: popq %rbx LCFI10: popq %rbp LCFI11: ret
最后: x * x
方法不需要include
cmath
,这会让你的编译速度稍微快一些。