使用此代码有什么好处
double x;
double square = pow(x,2);
而不是这个?
double x;
double square = x*x;
我更喜欢x * x并且查看我的实现(Microsoft)我发现pow没有优势,因为对于特定的方形情况,x * x比pow更简单。
是否有特殊情况下战俘优越?
答案 0 :(得分:54)
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)
永远不会比简单乘法更多。< / p>
答案 1 :(得分:23)
在现代编译器上,例如g ++ 4.x,std :: pow(x,2)将被内联,如果它甚至不是编译器内置的,并且强度减少到x * x。如果不是默认情况下并且您不关心IEEE浮点类型一致性,请查看编译器手册以获得快速数学开关(g ++ == -ffast-math)。
旁注:有人提到包括math.h会增加程序大小。我的回答是:
在C ++中,您
#include <cmath>
,不是math.h 。此外,如果你的编译器不是很老,它只会增加你的程序大小(通常情况下),如果你的std :: pow实现只是内联到相应的x87指令,那么现代的g ++用x * x强度降低x²,然后没有相应的尺寸增加。此外,程序大小永远不应该决定你的代码是多么富有表现力。
cmath比math.h的另一个优点是,使用cmath,你得到每个浮点类型的std :: pow重载,而使用math.h,你可以在全局命名空间中得到pow,powf等,所以cmath提高了代码的适应性,特别是在编写模板时。
作为一般规则:首选表达式和清晰的代码,而不是基于可疑的性能和二进制大小的合理代码。
另见Knuth:
“我们应该忘记小的效率,大约97%的时间说:过早的优化是所有邪恶的根源”
和杰克逊:
程序优化的第一条规则:不要这样做。程序优化的第二条规则(仅限专家!):不要这样做。
答案 2 :(得分:13)
x*x
不仅更清晰,而且至少与pow(x,2)
一样快。
答案 3 :(得分:10)
这个问题触及了大多数关于科学编程的C和C ++实现的关键弱点之一。从Fortran转到C大约二十年,之后转到C ++,这仍然是那些偶尔让我怀疑这种转换是否是一件好事的痛处之一。
问题简而言之:
pow
的最简单方法是Type pow(Type x; Type y) {return exp(y*log(x));}
x*x
相比,使用pow(x,2)
的简单方法在计算上非常昂贵且失去了精确度。与针对科学编程的语言比较:
pow(x,y)
。这些语言具有内置的取幂运算符。 C和C ++坚决拒绝实现取幂运算符,这使得许多科学程序员的血液沸腾了。对于一些顽固的Fortran程序员来说,仅凭这一点就是永远不会切换到C的理由。依靠高优化级别来“做正确的事”是一个问题。我曾为多家禁止在安全关键软件中使用优化的组织工作过。在这里损失1000万美元,其中有1亿美元,记忆可能会很长(数十年之久),这都归功于某些优化编译器中的错误。
恕我直言,在C或C ++中,永远不会使用pow(x,2)
。在这个意见中,我并不孤单。使用pow(x,2)
的程序员通常会在代码审查期间重新加入。
答案 4 :(得分:9)
在C ++ 11中,有一种情况是使用x * x
优于std::pow(x,2)
,这种情况是您需要在constexpr中使用它的地方:
constexpr double mySqr( double x )
{
return x * x ;
}
我们可以看到std::pow未标记为 constexpr ,因此无法在 constexpr 函数中使用。
否则从性能角度看,将以下代码放入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
我们应该期待来自任何现代编译器的类似结果。
值得注意的是,目前gcc considers pow a constexpr,也涵盖了here,但这是一个不合规的扩展,不应该依赖,并且可能会在gcc
的后续版本中发生变化。
答案 5 :(得分:7)
x * x
将始终编译为简单的乘法。 pow(x, 2)
很可能(但绝不保证)会对其进行优化。如果没有进行优化,则可能会使用缓慢的通用升频数学例程。因此,如果您关注性能,则应始终支持x * x
。
答案 6 :(得分:6)
恕我直言:
pow(x, 6)
,可能会实现特定处理器的某些浮点机制等。干杯
答案 7 :(得分:1)
我可能会选择std::pow(x, 2)
因为它可以让我的代码重构更容易。一旦代码优化,它就没有任何区别。
现在,这两种方法并不完全相同。这是我的测试代码:
#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
,我只是一直使用它。
第一:调试时(零优化),生成的组件不同;这是相关部分:
# 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)
更清楚地传达了你的意图,但这是一个偏好问题,而不是表现。
即使对于更复杂的表达式,优化似乎也适用。举个例子:
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
这会使你的编译在其他条件相同的情况下变得更快。