就编译时评估而言,我很好奇我能把gcc推到什么程度,所以我让它计算Ackermann函数,特别是输入值为4和1(任何高于此值的值都是不切实际的(:
consteval unsigned int A(unsigned int x, unsigned int y)
{
if(x == 0)
return y+1;
else if(y == 0)
return A(x-1, 1);
else
return A(x-1, A(x, y-1));
}
unsigned int result = A(4, 1);
(我认为递归深度限制在~16K,但为了安全起见,我用-std=c++20 -fconstexpr-depth=100000 -fconstexpr-ops-limit=12800000000
编译了这个(
毫不奇怪,这占用了大量的堆栈空间(事实上,如果以默认的进程堆栈大小8mb运行,它会导致编译器崩溃(,并且需要几分钟的计算时间。然而,它最终确实到达了那里,所以显然编译器可以处理它
之后,我决定尝试使用模板、元函数和部分专业化模式匹配来实现Ackermann函数。令人惊讶的是,以下实现只需要几秒钟的评估时间:
template<unsigned int x, unsigned int y>
struct A {
static constexpr unsigned int value = A<x-1, A<x, y-1>::value>::value;
};
template<unsigned int y>
struct A<0, y> {
static constexpr unsigned int value = y+1;
};
template<unsigned int x>
struct A<x, 0> {
static constexpr unsigned int value = A<x-1, 1>::value;
};
unsigned int result = A<4,1>::value;
(用-ftemplate-depth=17000
编译(
为什么评估时间会有如此显著的差异?这些不是本质上等价的吗?我想我可以理解consteval
解决方案需要稍微更多的内存和评估时间,因为从语义上讲,它由一堆函数调用组成,但这并不能解释为什么在运行时计算的这个完全相同的(非常量(函数只比元函数版本(在没有优化的情况下编译(花费稍长的时间。
consteval
为什么这么慢?元函数版本怎么会这么快?它实际上并不比优化的机器代码慢多少。
在A
的模板版本中,当实例化特定的专业化(如A<2,3>
(时,编译器会记住此类型,并且不再需要实例化它。这源于这样一个事实,即类型是独特的;呼叫";对这个元函数来说只是计算一个类型。
consteval
函数版本没有为此进行优化,因此A(2,3)
可能会根据控制流进行多次评估,从而导致您观察到的性能差异。没有什么能阻止编译器";高速缓存";函数调用的结果,但这些优化可能还没有实现。