如何快速评估const expr


13

我一直在尝试在编译时求值的const表达式。但是我玩了一个在编译时执行起来似乎很快的示例。

#include<iostream> 

constexpr long int fib(int n) { 
    return (n <= 1)? n : fib(n-1) + fib(n-2); 
} 

int main () {  
    long int res = fib(45); 
    std::cout << res; 
    return 0; 
} 

当我运行此代码时,大约需要7秒钟才能运行。到目前为止,一切都很好。但是当我切换long int res = fib(45)const long int res = fib(45)它时,甚至不需要一秒钟。据我了解,它是在编译时评估的。 但是编译大约需要0.3秒

编译器如何如此迅速地进行评估,但是在运行时却要花费更多的时间?我正在使用gcc 5.4.0。


7
我猜想编译器会将函数调用缓存到fib。您上面拥有的斐波那契数字的实现速度很慢。尝试将函数值缓存在运行时代码中,它将更快。
n314159

4
这种递归斐波那契效率极低(它具有指数运行时间),所以我的猜测是编译时评估比这更聪明,并且可以优化计算。
大火

1
@AlanBirtles是的,我用-O3编译了它。
Peter234

1
我假设编译器缓存函数调用,该函数只需要被遍历46次(每个可能的参数0-45一次),而不是2 ^ 45次。但是我不知道gcc是否可以那样工作。
churill

3
我知道@Someprogrammerdude。但是,当评估在运行时花费大量时间时,如何快速编译呢?
Peter234

Answers:


5

编译器缓存较小的值,不需要像运行时版本那样重新计算。
(优化程序非常好,它会生成很多代码,包括一些特殊情况下的骗术,这对我来说是难以理解的;幼稚的2 ^ 45递归将花费数小时。)

如果还存储以前的值:

int cache[100] = {1, 1};

long int fib(int n) {
    int res = cache[n];
    return res ? res : (cache[n] = fib(n-1) + fib(n-2));
} 

运行时版本比编译器快得多。


除非您进行一些缓存,否则无法避免重复两次。您认为优化器实现了某些缓存吗?您是否可以在编译器输出中显示此内容,因为那真的很有趣?
苏玛

...也有可能编译器而不是缓存编译器能够证明fib(n-2)和fib(n-1)之间的某种关系,而不是调用它用于fib(n-2)的fib(n-1) )值进行计算。我认为这与我在5.4的输出中看到的一致,即删除constexpr并使用-O2。
苏马

1
您是否有一个链接或其他来源说明在编译时可以进行哪些优化?
彼得234

只要可观察到的行为保持不变,优化器就可以自由地执行几乎所有操作。给定的fib函数没有副作用(引用没有外部变量,输出仅取决于输入),使用聪明的优化器可以完成很多工作。
苏马

@Suma只需重复一次就没问题。由于有一个迭代版本,因此当然也有一个递归版本,它使用例如尾递归。
Ctx

1

您可能会发现有趣的是5.4功能并未完全消除,为此您至少需要6.1。

我认为没有任何缓存发生。我坚信优化器足够聪明,可以证明fib(n - 2)和之间的关系,并且fib(n-1)完全避免了第二次调用。这是GCC 5.4输出(从Godbolt获得),不包含constexpr-O2:

fib(long):
        cmp     rdi, 1
        push    r12
        mov     r12, rdi
        push    rbp
        push    rbx
        jle     .L4
        mov     rbx, rdi
        xor     ebp, ebp
.L3:
        lea     rdi, [rbx-1]
        sub     rbx, 2
        call    fib(long)
        add     rbp, rax
        cmp     rbx, 1
        jg      .L3
        and     r12d, 1
.L2:
        lea     rax, [r12+rbp]
        pop     rbx
        pop     rbp
        pop     r12
        ret
.L4:
        xor     ebp, ebp
        jmp     .L2

我必须承认,我不了解-O3的输出-生成的代码非常复杂,具有大量的内存访问和指针算术,并且很可能使用这些设置进行了一些缓存(内存化)。


我想我错了。.L3处有一个循环,且fib遍历所有较低的纤维。使用-O2时,它仍然是指数。
苏玛
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.