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

phi*_*131 5 c++ optimization function

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

假设我有这样的代码片段:

void fairlyComplexFunction(const double &angle)
{
    //do stuff and call "sin(angle)" very often in here
}
Run Code Online (Sandbox Code Playgroud)

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

void fairlyComplexFunction(const double &angle)
{
    const double sineOfAngle = sin(angle);
    //do stuff and use sineOfAngle very often in here
}
Run Code Online (Sandbox Code Playgroud)

编译器是否能够以任何方式检测此类内容并为我优化它,或者第二个示例是更好的方法吗?

Hol*_*olt 6

正如评论中已经指出的,如果编译器可以检测到被调用的函数是纯函数(没有副作用,没有 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);
Run Code Online (Sandbox Code Playgroud)

您对该函数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
        ...
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

  • @phil13131:有一个理论上的担忧,即舍入模式可能在两次调用之间发生变化,这将使函数实际上不纯。使用“-ffast-math”,编译器可以忽略舍入模式的更改。 (3认同)