假设我有一个函数double F(double x)
,就本例而言,我们假设对F
的调用成本很高。
假设我写了一个函数f
来计算F
的平方根:
double f(double x){
return sqrt(F(x));
}
在第三个函数sum
中,我计算出f
和F
的总和:
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
来优化我的代码?
非常感谢。
答案 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