使用这段代码是否有优势?
double x;
double square = pow(x,2);
换成这个怎么样?
double x;
double square = x*x;
我更喜欢使用x*x,而且在我所使用的Microsoft实现中,对于特定的平方情况,我发现使用pow没有任何优势,因为x*x更简单。
是否有任何情况下pow更占优势?
使用这段代码是否有优势?
double x;
double square = pow(x,2);
换成这个怎么样?
double x;
double square = x*x;
我更喜欢使用x*x,而且在我所使用的Microsoft实现中,对于特定的平方情况,我发现使用pow没有任何优势,因为x*x更简单。
是否有任何情况下pow更占优势?
值得一提的是,在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)
永远不会比纯乘法更优。
-O6
和 -O3
是相同的。g++ 并不会调到11。 - Tobupow(x, 2)
和x * x
生成相同的代码。它总是调用pow
函数,导致一系列分支和大量缓慢的代码。如果你的意思是x * x
,那么请将其写成x * x
。避免使用过于通用的函数来显示自己的聪明才智。我们正在讨论一个巨大的速度差异。因此,你的答案是非常误导人的。如果你要分析汇编代码,至少需要反汇编相关编译器生成的代码,特别是因为提问者确实提到了它。 - Cody Graystd::pow
更加表达性强,如果您的意思是x²,而x*x
在表示x*x时更具表达性,特别是当您仅编写科学论文并且读者应该能够理解您的实现与论文时。对于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
更有优势的另一个方面是,它为每种浮点数类型提供了std::pow
重载函数,而使用math.h
则在全局命名空间中提供了pow
,powf
等,因此cmath
增加了代码的适应性,特别是在编写模板时。
一般而言:相对于基于性能和二进制大小的不可靠推测的代码,更偏爱表达清晰的代码。
另请参见 Knuth:
"我们应该忘记小小的效率问题,在97%的情况下都是如此:过早优化是万恶之源。"
以及 Jackson:
程序优化的第一条规则:不要去做优化。程序优化的第二条规则(仅适用于专家):暂时不要进行优化。
x*x
不仅更清晰,而且肯定至少与pow(x,2)
一样快。
pow(x, 2)
进行优化——请参见我的回答。 - Alnitak1.0 / (x*x*sqrt(x))
替换pow(x, -2.5)
。在进行计算的特定机器上,这给出了令人难以置信的加速效果。(编译器是gcc4.4,如果我没记错的话) - Sven Marnach这个问题涉及到大多数使用C和C++进行科学编程实现的主要弱点之一。在从Fortran转向C约20年后,然后转向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。仅依赖高优化级别“恰当地处理”存在问题。我曾为多个组织工作,这些组织禁止在安全关键软件中使用优化。在某些优化编译器中出现错误导致损失了数百万到数亿美元后,记忆可能会非常长(长达数十年)。
在C或C ++中,我认为永远不应该使用pow(x,2)
。我的观点并不孤立。那些使用pow(x,2)
的程序员通常在代码审查中受到严厉批评。
std::vector
(甚至不是std::valarray
)进行高性能数学向量的计算。 - Christian Rau在C++11中,只有在需要在constexpr中使用时,才比std::pow(x,2)
更好的情况是使用x * x
:
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将pow视为constexpr,这也在此处进行了介绍,但这是一种不符合规范的扩展,不能依赖它,并且在后续版本的gcc
中可能会发生改变。
x * x
编译后将始终简化为乘法。pow(x, 2)
可能会被优化为相同的形式,但不保证。如果没有被优化,那么它很可能使用缓慢的一般幂计算方法。因此,如果性能是您关心的问题,您应该始终选择x * x
。
我的看法:
pow(x, 6)
,可能会实现特定处理器的浮点机制等。干杯
pow(x,2)
并不比x*x
更糟糕。至少它给出了一些分组;如果你有可读性问题,你应该将计算拆分,常量也会胜出:const float x = pow(x,2) / sqrt(y)
-> const float num = pow(x,2), denom = sqrt(y), x = num/denom;
。当然最好的是x²
,但这是C++。 - Sebastian Machstd::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);
}
< p > asm("text");
调用只是将注释写入汇编输出中,我使用(GCC 4.8.1 在 OS X 10.7.4 上)生成此输出:
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
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
,这会使您的编译速度稍微更快,其他条件相同。