How does the C++ compiler evaluate recursive constexpr functions so quickly?

constexpr functions have no side-effects and can thus be memoized without worry. Given the disparity in runtime the simplest explanation is that the compiler memoizes constexpr functions during compile-time. This means that fibonacci(n) is only computed once for each n, and all other recursive calls get returned from a lookup table.


In g++ 8.3.0, if you use constexpr in this case, it computes the value you are using and outputs the result as a constant. This is even without optimizations:

//#include <iostream>

constexpr long long fibonacci(int num){
    if(num <= 2){return 1;}
    return fibonacci(num - 1) + fibonacci(num - 2);
}

int main(int argc, char** argv)
{

    //double start = clock();
    long long num = fibonacci(70);
    //std::cout << num << std::endl;
    //cout << (clock()-start)/(CLOCKS_PER_SEC/1000) << endl;

    return 0;
}
        .file   "constexpr.cc"
        .text
        .globl  main
        .type   main, @function
main:
.LFB1:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        movl    %edi, -20(%rbp)
        movq    %rsi, -32(%rbp)
        movabsq $190392490709135, %rax
        movq    %rax, -8(%rbp)
        movl    $0, %eax
        popq    %rbp
        .cfi_def_cfa 7, 8
        ret
        .cfi_endproc
.LFE1:
        .size   main, .-main
        .ident  "GCC: (Debian 8.3.0-6) 8.3.0"
        .section        .note.GNU-stack,"",@progbits

I was wondering why there is so huge difference between the code and the compiler in terms of execution time.

It seems it computes it without recursion. With recursion is just too slow.

What surprises me is that it can convert a recursive function into a iterative one, even without optimization, at compile time. At least that's what it seems.


To add some details to what other's pointed out: constexpr function doesn't have to be computed at runtime and one of the parameters that can affect it is -fconstexpr-ops-limit.

On GCC 10.2.0, -fconstexpr-ops-limit=1000000000 (1B) and fibonacci(40) results in a pre-compiled value, but if you drop the limit to 10000000 (10M) then function is computed at run-time. If you want to make sure the value is always computed at compile time, you need to mark long long num as constexpr in addition to the fibonacci function.

Note: the opposite example would be a non-constexpr function computed at compile time (optimized out) and marking it with __attribute__ ((const)) might help compiler make such decision. However, my compiler didn't optimize it out.