问题描述
|
使用此代码有什么好处
double x;
double square = pow(x,2);
而不是这个?
double x;
double square = x*x;
我更喜欢x * x并查看我的实现(Microsoft),在pow中我没有发现任何优势,因为对于特定的方形情况,x * x比pow更简单。
在某些情况下战俘是优越的吗?
解决方法
FWIW,在MacOS X 10.6上带有gcc-4.2和
-O3
编译器标志,
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),(%rsi)
leave
ret
因此,只要您使用的是体面的编译器,就对您的应用程序编写更有意义的代码,但请注意,pow(x,2)
永远不会比普通乘法更好。
, 如果您的意思是x²,std :: pow更具表现力,如果您的意思是xx则xx更具表达力,尤其是在您只是编码例如科学论文,读者应该能够理解您的实现与论文。对于x * x /x²来说,差别可能很小,但是我认为,如果通常使用命名函数,它会提高代码的可扩展性和可读性。
在现代编译器上,例如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%的时间是这样:过早的优化是万恶之源”
和杰克逊:
程序优化的第一条规则:不要这样做。程序优化的第二条规则(仅适用于专家!):暂时不要做。
, ѭ9不仅更清晰,而且肯定至少和ѭ10一样快。
,这个问题触及了关于科学编程的大多数C和C ++实现的关键弱点之一。从Fortran切换到C大约20年,后来又切换到C ++之后,这仍然是一个痛苦的地方之一,有时使我怀疑是否进行切换是一件好事。
简而言之,这个问题是:
实施pow
的最简单方法是Type pow(Type x; Type y) {return exp(y*log(x));}
大多数C和C ++编译器都采取了简单的方法。
有些人可能会“做正确的事”,但只能在高优化级别上进行。
与x*x
相比,使用pow(x,2)
的简便方法在计算上非常昂贵,并且会失去精度。
与旨在科学编程的语言相比:
你不写ѭ15。这些语言具有内置的幂运算符。 C和C ++一直坚决拒绝实现幂运算符,这使许多科学程序员的血液都沸腾了。对于一些顽固的Fortran程序员而言,仅此一个原因就永远不要切换到C。
对于所有小的整数幂,要求Fortran(和其他语言)“做正确的事”,其中small是-12到12之间的任何整数。(如果编译器不能做到,则编译器不兼容)正确的事情。)此外,还要求他们关闭优化功能。
许多Fortran编译器还知道如何在不求助于简单方法的情况下提取一些有理根源。
依靠高优化级别来“做正确的事”是一个问题。我曾在多家禁止在安全关键软件中使用优化的组织工作过。在这里损失1000万美元,在这里损失1亿美元之后,内存可能会很长(数十年之久),这都是由于某些优化编译器中的错误所致。
恕我直言,不要在C或C ++中使用ѭ10。我并不孤单。确实使用ѭ10的程序员通常会在代码审查期间花费大量时间。
, 在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,D.4292
ret
我们应该从任何现代编译器中获得相似的结果。
值得一提的是,目前gcc认为pow是constexpr,在此也进行了介绍,但这是不合格的扩展,不应依赖它,并且可能会在later23ѭ的更高版本中发生更改。
, x * x
将始终编译为简单的乘法。 pow(x,2)
可能会(但绝对不能保证)被优化到相同的水平。如果未对它进行优化,则可能使用了缓慢的通用上电数学例程。因此,如果您关心性能,则应始终偏爱x * x
。
, 恕我直言:
代码可读性
代码健壮性-更容易更改为pow(x,6)
,也许实现了针对特定处理器的某种浮点机制,等等。
性能-如果有一种更智能,更快速的方法来计算此结果(使用汇编器或某种特殊技巧),pow会做到这一点。你不会.. :)
干杯
, 我可能会选择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,%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,%xmm0
leave
LCFI5:
ret
LFE238:
.text
.globl __Z14square_libraryd
__Z14square_libraryd:
LFB237:
pushq %rbp
LCFI6:
movq %rsp,%rbp
LCFI7:
subq $16,-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,%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
因此,除非您关心未优化的代码的速度,否则它实际上没有任何区别。
就像我说的:在我看来,“ 28”可以更清楚地传达您的意图,但这只是优先考虑的问题,而不是表现。
对于更复杂的表达式,优化似乎也适用。举个例子:
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,%rsp
LCFI9:
popq %rbx
LCFI10:
popq %rbp
LCFI11:
ret
最后:ѭ18方法不需要include
42ѭ,这将使您的编译在其他所有条件相同的情况下都稍快一些。