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

时间:2019-03-28 21:06:38

标签: c++ c optimization compiler-optimization

假设我有一个函数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来优化我的代码?

非常感谢。

3 个答案:

答案 0 :(得分:8)

  

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

也许。两种语言都不需要这种优化,是否允许这种优化取决于F()的实现细节。一般来说,对于这种情况,不同的编译器的行为会有所不同。

完全有可能的是,编译器会将函数f()内联到函数sum()中,这将使它有机会认识到有两个对F(x)的调用导致了相同的结果。在这种情况下,如果F()没有副作用,则可以想象编译器将只发出对F()的单个调用,从而重用结果。

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

答案 1 :(得分:2)

您所描述的称为memoization,是一种(通常)运行时缓存的形式。尽管可以在编译器中实现此功能,但通常在C编译器中执行。

C ++确实有一个聪明的解决方法,使用几年前详细介绍的at this blog post的STL;还有一个最近的SO答案here。值得注意的是,使用这种方法,编译器不是“聪明地”推断出函数的多个相同结果将被重用,但是效果大体上相同。

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

答案 2 :(得分:1)

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

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

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

gcc,clang和其他许多语言可以使用的提示是here,它们分别是__attribute__((pure))__attribute__((const))。例如,将GNU strlen声明为纯函数。

GCC可以检测到pure functions,并建议程序员应该将哪些函数纯合为一体。实际上,对于以下简单示例,它会自动执行此操作:

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 see the compilation result来使用gcc。在整个my_strlen(str)函数中,它仅调用一次word_len。 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