C/C++编译器是否会通过重用最近计算的函数结果来优化代码?

Will a C/C++ compiler optimise code by reusing a recently calculated function result?

本文关键字:函数 计算 结果 代码 优化 最近 编译器 C++ 是否      更新时间:2023-10-16

>假设我有一个函数double F(double x),为了这个例子,让我们假设调用F是昂贵的。

假设我编写一个函数f来计算F的平方根:

double f(double x){
return sqrt(F(x));
}

在第三个函数sum我计算fF之和:

double sum(double x){
return F(x) + f(x);
}

由于我想最大限度地减少对F的调用,因此与例如

double sum_2(double x){
double y = F(x);
return y + sqrt(y);
}

但是由于我很懒惰,或者愚蠢,或者想让我的代码尽可能清晰,所以我选择了第一个定义。

C/C++编译器是否会通过意识到F(x)的值可以重用于计算f(x)来优化我的代码,就像在sum_2中所做的那样?

非常感谢。

C/C++ 编译器是否会通过意识到 F(x) 的值可以重用于计算 f(x) 来优化我的代码,就像在 sum_2 中所做的那样?

或。 这两种语言都不需要这样的优化,它们是否允许取决于F()实现的细节。 一般来说,不同的编译器在这类事情上的行为不同。

编译器将函数f()内联到函数sum()中是完全合理的,这将使它有机会认识到有两个调用F(x)对相同的结果有贡献。 在这种情况下,如果F()没有副作用,那么可以想象编译器只会发出一次调用F(),重用结果。

特定的实现可能具有扩展,可用于帮助编译器得出这样的结论。 但是,如果没有这样的扩展应用于问题,我认为编译器不太可能发出仅执行一次调用F()并重用结果的代码。

你所描述的称为记忆,一种(通常)运行时缓存的形式。虽然这可以在编译器中实现,但通常不会在 C 编译器中执行。

C++确实有一个聪明的解决方法,使用 STL,详见几年前的这篇博文;这里还有一个稍微更新的 SO 答案。值得注意的是,使用这种方法,编译器不会"聪明地"推断函数的多个相同结果将被重用,但效果大致相同。

某些语言(如 Haskell)确实具有对编译时记忆的内置支持,但编译器体系结构与 Clang 或 GCC/G++ 有很大不同。

许多编译器使用提示来确定是否可以重用先前函数调用的结果。一个经典的例子是:

for (int i=0; i < strlen(str); i++)

如果不对此进行优化,此循环的复杂度至少为 O(n2),但优化后它可以是 O(n)。

gcc,clang和许多其他人可以采取的提示是这里描述的__attribute__((pure))__attribute__((const))。例如,GNUstrlen被声明为纯函数。

GCC可以检测纯函数,并建议程序员哪些函数应该与纯函数结婚。实际上,对于以下简单示例,它会自动执行此操作:

unsigned my_strlen(const char* str) 
{
int i=0;
while (str[i])
++i;
return i;
} 
unsigned word_len(const char *str)
{
for (unsigned i=0 ; i < my_strlen(str); ++i) {
if (str[i] == ' ')
return i;
}
return my_strlen(str);
}

您可以使用-O3 -fno-inline查看 gcc 的编译结果。它在整个word_len函数中只调用my_strlen(str)一次。Clang 7.0.0 似乎没有执行此优化。

word_len:
mov     rcx, rdi
call    my_strlen ; <-- this is the only call (outside any loop)
test    eax, eax
je      .L7
xor     edx, edx
cmp     BYTE PTR [rcx], 32
lea     rdi, [rdi+1]
jne     .L11
jmp     .L19
.L12:
add     rdi, 1
cmp     BYTE PTR [rdi-1], 32
je      .L9
.L11:
add     edx, 1
cmp     eax, edx
jne     .L12
.L7:
ret
.L19:
xor     edx, edx
.L9:
mov     eax, edx
ret