使用pow(x,2)代替x * x,x double是否有任何优势?

时间:2011-06-12 09:18:16

标签: c++ math floating-point

使用此代码有什么好处

double x;
double square = pow(x,2);

而不是这个?

double x;
double square = x*x;

我更喜欢x * x并且查看我的实现(Microsoft)我发现pow没有优势,因为对于特定的方形情况,x * x比pow更简单。

是否有特殊情况下战俘优越?

8 个答案:

答案 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)

如果你的意思是x²,那么std :: pow更具表现力,如果你的意思是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的另一个优点是,使用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));}
  • 大多数C和C ++编译器都采用简单的方法。
  • 有些人可能会“做正确的事”,但只能在高优化级别。
  • x*x相比,使用pow(x,2)的简单方法在计算上非常昂贵且失去了精确度。

与针对科学编程的语言比较:

  • 你不写pow(x,y)。这些语言具有内置的取幂运算符。 C和C ++坚决拒绝实现取幂运算符,这使得许多科学程序员的血液沸腾了。对于一些顽固的Fortran程序员来说,仅凭这一点就是永远不会切换到C的理由。
  • Fortran(和其他语言)需要为所有小整数幂做'正确的事',其中small是-12到12之间的任何整数。(如果编译器不能执行正确的事情'。)此外,他们需要优化关闭。
  • 许多Fortran编译器也知道如何在不诉诸简单方法的情况下提取一些理性根源。

依靠高优化级别来“做正确的事”是一个问题。我曾为多家禁止在安全关键软件中使用优化的组织工作过。在这里损失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),可能会实现特定处理器的某些浮点机制等。
  • 性能 - 如果有更智能,更快速的方法来计算(使用汇编程序或某种特殊技巧),pow会做到这一点。你不会.. :))

干杯

答案 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这会使你的编译在其他条件相同的情况下变得更快。