编译器可以优化多个相同的函数调用吗

Posted

技术标签:

【中文标题】编译器可以优化多个相同的函数调用吗【英文标题】:Can a compiler optimize multiple same function calls 【发布时间】:2016-06-17 08:35:59 【问题描述】:

关于 SO 上的“冗余函数调用”的编译器优化有很多问题和很好的答案(我不会发布链接),但是,我在 多个相同 函数调用上找不到任何东西就这样。

假设我有这样的代码 sn-p:

void fairlyComplexFunction(const double &angle)

    //do stuff and call "sin(angle)" very often in here

调用sin(angle) 是一个相当昂贵的操作,因为anglefairlyComplexFunction 范围内的常量,所以每次调用正弦函数都会得到相同的结果,所以只调用它一次是更好的方法:

void fairlyComplexFunction(const double &angle)

    const double sineOfAngle = sin(angle);
    //do stuff and use sineOfAngle very often in here

编译器是否能够以任何方式检测到此类事情并为我优化,还是第二个示例是更好的方法?

【问题讨论】:

自己编译看看输出? @KerrekSB 首先,我真的不擅长查看和理解汇编语言。另外,仅仅因为当我编译它并且它不起作用时,并不意味着其他编译器可能无法做到。 原则上,如果编译器可以确定所讨论的函数是一个函数(没有副作用,并且唯一影响其输出的是它的输入, ) 然后它可以轻松消除多个调用。然而,在 C++ 中证明一个函数是纯函数并不总是那么容易或微不足道的。 @yzt 在这种情况下,sin(x) 确实是一个纯函数,所以我猜它“原则上可以工作”? @phil13131:不仅是一个纯函数,而且是实现提供的函数。这不仅仅是“原则上可行”,我实际上是期待的。我什至希望对sin(x)cos(x) 的调用能够合并,即使它们不是相同的函数(!) 【参考方案1】:

正如评论中已经提到的,如果编译器可以检测到被调用的函数是一个纯函数(没有副作用,没有 i/o,.. .).

一个使用 g++ 的小例子 (https://godbolt.org/g/2b3Vgg):

#include <cmath>

extern double g (double);

template <double (*F) (double)>
double f1 (double angle) 
  double x = 3 * F(angle) + F(angle);
  double y = F(angle) + F(angle) * F(angle);
  return x + y * F(angle);


template double f1<sin> (double);
template double f1<g> (double);

f1 中,您有多次调用F 函数,以及两个实例:

带有std::sin 的一个 - 任何理智的编译器都应将其视为纯函数。 带有extern 函数的函数不能被视为纯函数。

如果查看生成的程序集*:

double f1<&sin>(double):
        subq    $8, %rsp
        call    sin
        ...
        ret

double f1<&(g(double))>(double):
        subq    $40, %rsp
        movsd   %xmm0, (%rsp)
        call    g(double)
        movsd   %xmm0, 8(%rsp)
        movsd   (%rsp), %xmm0
        call    g(double)
        movsd   8(%rsp), %xmm1
        mulsd   .LC0(%rip), %xmm1
        ...
        call    g(double)
        movsd   %xmm0, 16(%rsp)
        movsd   (%rsp), %xmm0
        call    g(double)
        movsd   %xmm0, 24(%rsp)
        movsd   (%rsp), %xmm0
        call    g(double)
        mulsd   24(%rsp), %xmm0
        movsd   16(%rsp), %xmm2
        ...
        call    g(double)
        mulsd   16(%rsp), %xmm0
        addsd   8(%rsp), %xmm0
        ...

您会看到,在sin 的实例化中,g++ 只执行一个函数调用 (call sin),而在g 的实例化中,您有 6 个调用。

所以是的,编译器可能会针对多次调用纯函数进行一些优化,但我不会依赖它**并使用显式的中间变量,如您的第二个例子。

* 我删除了大部分生成的指令,但显示了所有 call 指令。

** clang 即使使用 -O3 也不会对此进行优化(但使用 -ffast-math 会优化)。

【讨论】:

非常感谢,这是一个非常彻底的答案。我觉得这很有趣,它没有使用 -O3 进行优化,但你需要 -ffast-math ,因为这应该是多余的。同样棘手的是,用户“必须知道这一点”并且最终可能会得到未完全优化的代码...... @phil13131:理论上存在一些担忧,即舍入模式可能会在两次调用之间发生变化,这会使函数实际上不纯。使用-ffast-math,编译器可以忽略舍入模式的更改。【参考方案2】:

如果它可以证明sin没有任何副作用,它可以优化它。

但即使可以,您的第二个版本也向读者和您自己清楚地表明,所有代码应该使用相同的值,并且没有任何使用是错误;错误和不确定性的空间更小。

【讨论】:

以上是关于编译器可以优化多个相同的函数调用吗的主要内容,如果未能解决你的问题,请参考以下文章

python函数可以调用具有相同名称的全局函数吗?

c语言中,在一个自定义函数里面只能调用一个自定义函数吗?可以调用多个吗?如果可以怎么调用?

当多个线程调用相同的函数时,Java/C#如何分配内存

内联函数

static函数如何调用虚函数

opaque函数调用在编译器优化中意味着什么?