【问题标题】:Is there any advantage to using pow(x,2) instead of x*x, with x double?使用 pow(x,2) 代替 x*x 和 x double 有什么好处吗?
【发布时间】:2011-06-12 09:18:15
【问题描述】:

使用此代码有什么好处

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

而不是这个?

double x;
double square = x*x;

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

pow 有什么特殊的情况吗?

【问题讨论】:

标签: c++ math floating-point


【解决方案1】:

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) 永远不会比普通乘法最优。

【讨论】:

  • -O6-O3 是一样的。 g++ 不会达到十一。
  • 显然它甚至没有达到四个 :)
  • @Alnitak +1 我非常喜欢你查看汇编代码的方法。
  • 我一直对通过汇编来证明程序行为持谨慎态度。工具链(最重要的是标准)不保证汇编输出,因此它可能随时更改。除了您碰巧正在查看的特定两个可执行文件之外,证明程序的任何内容都不是万无一失的方法。
  • 不管怎样,微软的编译器绝对不会为pow(x, 2)x * x 生成相同的代码。它总是调用pow 函数,导致一系列分支和很多慢代码。如果你的意思是x * x,那么写成x * x。避免使用过于通用的函数只是为了聪明。我们在这里谈论的是 巨大 的速度差异。因此,您的回答非常具有误导性。如果要分析反汇编,至少需要对相关编译器生成的代码进行反汇编。特别是因为提问者确实懒得提及它。
【解决方案2】:

std::pow 如果您的意思是 则更具表现力,x*x 如果您的意思是 x*x 则更具表现力,尤其是当您只是在编写代码时,例如一篇科学论文,读者应该能够理解你的实现与论文。 x*x/ 的区别可能很微妙,但我认为如果您通常使用命名函数,它会增加代码的 expesiveness 和可读性。

在现代编译器上,例如g++ 4.x, std::pow(x,2) 将被内联,如果它甚至不是编译器内置的,并且强度降低到 x*x。如果不是默认情况下并且您不关心 IEEE 浮点类型一致性,请查看编译器手册以获取快速数学切换 (g++ == -ffast-math)。


旁注:已经提到包含math.h 会增加程序大小。我的回答是:

在 C++ 中,你是 #include &lt;cmath&gt;不是 math.h。此外,如果您的编译器不是老旧的,它只会根据您正在使用的内容(在一般情况下)增加您的程序大小,并且如果您的 std::pow 实现只是内联到相应的 x87 指令,而现代 g++ 将强度减少x*x,则没有相关的尺寸增加。此外,程序大小永远不应决定代码的表现力。

cmath 相对于math.h 的另一个优势是,使用cmath,您会为每种浮点类型获得std::pow 重载,而使用math.h,您会获得powpowf 等. 在全局命名空间中,因此cmath 增加了代码的适应性,尤其是在编写模板时。

作为一般规则:更喜欢表达性和清晰的代码,而不是可疑的基础性能和二进制大小的推理代码。

另见高德:

“我们应该忘记小的效率,比如大约 97% 的时间:过早的优化是万恶之源”

和杰克逊:

程序优化的第一条规则:不要这样做。程序优化的第二条规则(仅限专家!):不要这样做。

【讨论】:

  • +1,写任何源代码时遵守Principle of Least Astonishment确实很重要。
  • +1 用于各种事情,尤其是您为正确的上标 2 字符而烦恼:)
  • @Tomalak Geret'kal:在许多(大多数?)Linux 桌面发行版上,可以简单地使用 x¹²³⁴⁵⁶⁷⁸⁹⁰,当您需要它时,这当然非常棒 :) 另一方面,当我在windows,我并不羞于使用
  • 我不同意杰克逊的观点。优化(比如使用更好的算法)是应该一直做的事情。
  • @Cœur:问题在于这导致只编写最可怕的代码。这种情况会使公司陷入瘫痪,因为即使在最不重要的事情上,开发人员也不愿意牺牲他们所有的大脑潜力。我曾经与一家失去所有开发人员的公司合作。没有开发人员,没有产品,没有钱,没有公司。 “始终优化”能走多远?因为,如果有人要求这样做,我会深入研究比特技巧、缓存黑魔法和 SSE 程序集。我会编写巨大的宏和图灵完整的模板,为每个特殊情况重复代码,......不,我会退出。
【解决方案3】:

x*x 不仅更清晰,而且肯定至少与pow(x,2) 一样快。

【讨论】:

  • 几乎每个编译器都会更快,通常超过一个数量级。
  • @sven 我也这么认为,但我想编译器可以优化并将 pow 内联为简单的乘法。所以我很谨慎!
  • @David 确实编译器可以优化pow(x, 2) - 请参阅我的答案。
  • @Sven:你如何定义“几乎每个编译器”?如果你不关心 IEEE 一致性,你可以使用 g++ -fast-math,这样它会强度将 pow(x,2) 减少到 x*x,所以没有区别。
  • 出于性能原因,我们甚至不得不将pow(x, -2.5) 替换为1.0 / (x*x*sqrt(x))。在我们进行计算的特定机器上提供了令人难以置信的加速。 (编译器是 gcc 4.4,IIRC)
【解决方案4】:

这个问题涉及大多数 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) 的简单方法在计算上极其昂贵并且会损失精度。

与面向科学编程的语言相比:

  • 你不写pow(x,y)。这些语言有一个内置的幂运算符。 C 和 C++ 坚决拒绝实现指数运算符,这让许多科学程序员的热血沸腾。对于一些顽固的 Fortran 程序员来说,仅此一点就是永远不要切换到 C 的理由。
  • Fortran(和其他语言)需要为所有小整数幂“做正确的事”,其中 small 是介于 -12 和 12 之间的任何整数。(如果编译器不能“做正确的事情”。)此外,他们必须在优化关闭的情况下这样做。
  • 许多 Fortran 编译器也知道如何提取一些有理根,而无需求助于简单的方法。

依赖高优化级别来“做正确的事”存在问题。我曾为多个禁止在安全关键软件中使用优化的组织工作过。在这里损失 1000 万美元,那里损失 1 亿美元之后,内存可能会很长(数十年之久),这都是由于某些优化编译器中的错误。

恕我直言,不应该永远在 C 或 C++ 中使用 pow(x,2)。我并不孤单。确实使用 pow(x,2) 的程序员通常会在代码审查期间获得大量时间。

【讨论】:

  • 对于更复杂的类型(如向量和矩阵),没有专门的 pow 运算符(这并不难接受)得到了补偿。远离优化会适得其反(当然不应该在科学应用中进行)。如果您的代码在优化后不起作用,那只是错误的代码。您必须依赖某些东西(例如工作编译器)。但我同意不要依赖并非所有编译器都支持的高级优化功能(我想就像提到的 pow 优化)。
  • @christian 我很贪心。我想要这一切,运算符重载和 pow 运算符!
  • @Christian:你刚刚触及了另一个痛处。 C++ 对科学编程中使用的向量和矩阵的支持很差。首先,std::vector 不是向量。是的,还有一些顽固的 Fortran 程序员(或者更重要的是,禁止使用 C/C++ 的顽固项目经理),缺乏专门的求幂运算符太难接受了。
  • @David C++ 通过编写您自己的类(或使用现有库)来支持向量和矩阵,并且在它们上使用完全可用的运算符(并且使用表达式模板几乎不需要额外成本)。但是,是的,没有人愿意将std::vector(甚至std::valarray)用于高性能数学向量。
  • me 编写自己的类与支持矩阵和向量的 C++ 完全不同。与顽固的 Fortran 程序员交谈。缺乏指数运算符是他们看不起 C/C++ 的第二个原因。原因 #1 是缺乏对矩阵和向量的支持。原因 #3 是该语言提供的极其稀疏的数学库。
【解决方案5】:

在 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 的后续版本中发生变化。

【讨论】:

  • 不错,非常不错。不知道那个网站。
  • @phresnel 我发现它有助于大致了解编译器将快速执行哪些优化,并且对回答优化问题非常有帮助。
【解决方案6】:

x * x 将始终编译为简单的乘法。 pow(x, 2) 很可能,但绝不保证会优化到相同的值。如果它没有优化,它可能会使用一个缓慢的通用提升功率数学例程。因此,如果您关心性能,您应该始终支持x * x

【讨论】:

    【解决方案7】:

    恕我直言:

    • 代码可读性
    • 代码稳健性 - 将更容易更改为 pow(x, 6),可能为特定处理器实现了一些浮点机制,等等。
    • 性能 - 如果有更智能和更快的方法来计算(使用汇编程序或某种特殊技巧),pow 会做到。你不会.. :)

    干杯

    【讨论】:

    • 你关于 pow(x,6) 的论点是不现实的。编译器也可以优化 x*x!
    • @David 我同意它比另一个弱,但是这两者的结合已经足够强大了。优化论点对于这两种情况都是正确的,我认为这是一个口味问题(实际上整个问题是......)哪个更好。我赞成“不要做别人做过的事”的一般原则。
    • 如果该语言有一个内置的指数运算符,比如 fortran,那么讨论就没有意义了
    • 我也质疑可读性。 pow 在任何大型方程中都是可怕的。如果运算符重载被认为有利于可读性,那么 pow 应该被认为是有害的。
    • @David Heffernan:恕我直言,pow(x,2) 并不比x*x 可怕。至少它给出了一些分组;如果您有可读性问题,则无论如何都应该拆分计算, const 获胜:const float x = pow(x,2) / sqrt(y) -> const float num = pow(x,2), denom = sqrt(y), x = num/denom;。最好的当然是,但这是 C++。
    【解决方案8】:

    我可能会选择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"); 调用只是将 cmets 写入我使用(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 方法不需要 includeing cmath,这将使您的编译速度在其他条件相同的情况下稍微快一点。

    【讨论】:

      猜你喜欢
      • 2021-04-08
      • 2021-05-14
      • 2014-01-08
      • 2012-04-29
      • 1970-01-01
      • 2020-04-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多