C++ 编译时怎么能比运行时快(指数级)?

C++ 编译时怎么能比运行时快(指数级)?,c++,gcc,c++11,compiler-construction,constexpr,C++,Gcc,C++11,Compiler Construction,Constexpr,下面的代码通过指数级的慢速算法计算斐波那契数: #include <cstdlib> #include <iostream> #define DEBUG(var) { std::cout << #var << ": " << (var) << std::endl; } constexpr auto fib(const size_t n) -> long long { return n < 2 ? 1:

下面的代码通过指数级的慢速算法计算斐波那契数:

#include <cstdlib>
#include <iostream>

#define DEBUG(var) { std::cout << #var << ": " << (var) << std::endl; }

constexpr auto fib(const size_t n) -> long long
{
    return n < 2 ? 1: fib(n - 1) + fib(n - 2);
}

int main(int argc, char *argv[])
{
    const long long fib91 = fib(91);

    DEBUG( fib91 );
    DEBUG( fib(45) );

    return EXIT_SUCCESS;
}
#包括
#包括

#define DEBUG(var){std::coutGCC可能是在记忆
constepr
函数(允许对
fib(n)
进行Θ(n)计算)。这对于编译器来说是安全的,因为
constepr
函数是纯函数

将Θ(n)“编译器算法”(使用memoization)与Θ(φn)运行时算法(其中φ是黄金比例)进行比较,突然之间,编译器的速度快了很多,这是完全有道理的

起自(加强调):

constexpr说明符声明可以在编译时计算函数或变量的值

constexpr
说明符不声明在编译时计算函数或变量的值是必需的。因此,人们只能猜测GCC使用什么启发式方法来选择是在编译时计算,还是在语言ru不需要编译时计算的情况下在运行时计算les。它可以根据具体情况选择其中之一,但仍然是正确的

如果希望强制编译器在编译时计算
constepr
函数,下面是一个简单的技巧

constexpr auto compute_fib(const size_t n) -> long long
{
    return n < 2 ? n : compute_fib(n - 1) + compute_fib(n - 2);
}

template <std::size_t N>
struct fib
{
    static_assert(N >= 0, "N must be nonnegative.");
    static const long long value = compute_fib(N);
};
constexpr自动计算\u fib(const size\u t n)->long long
{
返回n<2?n:计算fib(n-1)+计算fib(n-2);
}
模板
结构纤维
{
静态断言(N>=0,“N必须是非负的”);
静态常数long long value=compute_fib(N);
};

在剩下的代码中,您可以访问
fib::value
fib::value
,并保证在编译时对其进行计算。

在编译时,编译器可以检查函数的结果。这是安全的,因为函数是一个constexpr,因此始终会返回相同输入的相同结果

在运行时,理论上可以做到这一点。然而,大多数C++程序员会在优化路径中皱眉,导致隐藏内存分配。

< P>当你请求FIB(91)在源代码中给你的confiB91提供一个值时,编译器被迫从你的const EXPR中计算这个值。它不编译函数。(就像你想的那样),只是它看到计算fib91需要fib(90)和fib(89),计算它需要fib(87)…依此类推,直到他计算给出的fib(1)。这是一个$O(n)$算法,计算结果足够快


但是,当您要求评估fib(45)时在运行时,编译器必须选择是使用实际函数调用还是预计算结果。最终它决定使用编译后的函数。现在,编译后的函数必须完全执行您确定的指数算法。编译器无法实现记忆以优化递归函数(考虑是否需要分配一些缓存,并了解要保留多少值以及如何在函数调用之间管理这些值).

可能
45
超过递归限制。使用
-fconstexpr depth=Max\u recursion\u limit
查看是否超过此限制。
返回退出成功+1;
在一个实验程序中。@Potatoswatter与执行
一样有用,定义按位的\u和\u运算符&
main结尾的下降时间是表示成功完成的常用和标准支持的方法:PFYI,这是补丁的一个版本(有一些描述)。我知道编译器如何将其更改为
O(n)
算法。我的问题是,为什么它对
fib(45)不这样做
?也就是说,它是否编译了两个版本的
fib
函数?@behzad.nouri应用于函数的
constepr
语义意味着函数返回值可以被视为编译时常量。他们不要求它被视为编译时常量。我用额外的信息更新了答案。我猜fib(n)的递归计算大约需要O(1.62^n)的时间。@MaartenHilferink你完全正确。我匆忙地写了Θ(2^n),但基数应该是黄金比率φ而不是2。这是因为复杂性实际上是Θ(fib(n)),其中fib(n)只是Θ(φ^n)。对于
constexpr
函数,应该完全有一个
runtime\u memoize
属性。对于
fib
,您很清楚,它只需要少量的输入就可以调用,因此缓存永远不会很大。但除了害怕程序员皱眉之外,我希望编译器会这样做-写程序的人害怕不知道缓存需要增长多大……当然,在使用缓存的过程中,程序员还必须接受分配内存失败时中止的行为,这不是每个人都想要的。@SteveJessop:问题是编译器不知道运行时输入是什么,所以它无法分配内存(足够)内存。需要在convers下有类似于
std::vector
的东西,然后在调整大小时会遇到异常安全问题……最好让程序员编写它,使其显式化。(现在,如果所有输入都有小的已知范围,这是可能的)我被这个答案弄糊涂了。在编译时,它必须计算小于
fib(91)
的必要值来赋值
fib91=fib(91)
。但是,编译器显然是在很短的时间内计算出来的。然后,在运行时,它必须再次这样做才能得到
fib45=fib(45)的值
,但是与查找
fib91=fib(91)
相比,查找
fib45=fib(45)
的计算要少得多,因此
fib45=fib(45)
应该更快,但根据问题,它要慢得多