用pow(x,2)代替x*x有什么好处吗
Is there any advantage to using pow(x,2) instead of x*x, with x double?
使用这个代码有什么好处吗
double x;
double square = pow(x,2);
而不是这个?
double x;
double square = x*x;
我更喜欢x*x,从我的实现(Microsoft)来看,我发现pow没有任何优势,因为对于特定的方形情况,x*x比pow更简单。
有没有什么特殊情况下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), %xmm0
mulsd %xmm0, %xmm0
movsd %xmm0, (%rsi)
leave
ret
因此,只要你使用的是一个不错的编译器,就可以编写对你的应用程序更有意义的编译器,但要考虑pow(x, 2)
永远不会比普通乘法更优化。
std::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²
,那么就不会增加相关的大小。此外,程序大小永远不应该决定代码的表达能力
与math.h
相比,cmath
的另一个优点是,使用cmath
,您可以为每个浮点类型获得std::pow
重载,而使用math.h
,您可以在全局命名空间中获得pow
、powf
等,因此cmath
提高了代码的适应性,尤其是在编写模板时。
一般来说:更喜欢表达清晰的代码,而不是基于可疑性能和二进制大小的合理代码
另请参见Knuth:
"我们应该忘记小效率,比如说97%的时间:过早优化是万恶之源"
和Jackson:
程序优化的第一条规则:不要做。程序优化的第二条规则(仅供专家使用!):还不要做。
x*x
不仅更清晰,而且肯定至少与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之间的任何整数。(如果编译器不能"做正确的事情",那么它就是不兼容的。)此外,它们必须在关闭优化的情况下这样做
- 许多Fortran编译器也知道如何提取一些有理根,而不必求助于简单的方法
依赖高优化级别来"做正确的事情"存在问题。我曾为多个组织工作,这些组织禁止在安全关键软件中使用优化。在这里损失了1000万美元,那里损失了1亿美元之后,内存可能会很长(几十年长),这一切都是由于某些优化编译器中的错误。
IMHO,应该永远不要在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函数中不可用。
否则,从性能的角度来看,将以下代码放入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
。
IMHO:
- 代码可读性
- 代码健壮性-将更容易更改为
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");
调用只是将注释写入程序集输出,我使用(OSX 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
,这将使您的编译速度稍微快一点——在其他条件相同的情况下。
- 为不同配置设置MSVC_RUNTIME_LIBRARY的正确方法是什么
- 警告处理为错误这里有什么问题
- 什么时候调用组成单元对象的析构函数
- #定义c-预处理器常量..我做错了什么
- 努力将整数转换为链表。不知道我在这里做错了什么
- C++我的数学有什么问题,为什么我的代码不能正确循环
- 什么时候在C++中返回常量引用是个好主意
- 当在同一名称空间中有两个具有相同签名的函数时,会发生什么
- C++避免重复声明的语法是什么
- c++库的公共头文件中应该包含什么
- 问题:什么是QAbstractItemView::NoEditTriggers的反面
- 有什么方法可以遍历结构吗
- 当类在C++中定义时,有什么方法可以"register"类吗?
- ifstream什么都没读
- 在C++中,将大的无符号浮点数四舍五入为整数的最佳方法是什么
- 循环c 时,此POW()在功能中起到了什么作用
- nan在C++中是什么意思?为什么pow(-4,-2.1)返回-nan
- 标准对std::pow、std::log等cmath函数有什么规定?
- 什么比std::pow快?
- 用pow(x,2)代替x*x有什么好处吗