c++ - 为什么 consteval/constexpr 和模板元函数之间的编译时间存在如此巨大的差异?

标签 c++ templates c++20 ackermann consteval

我很好奇就编译时评估而言,我可以将 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)函数只比元函数版本(编译时没有优化)花费的时间略长)。
为什么是 consteval太慢了?我几乎很想得出结论,它正在由 GIMPLE 解释器或类似的东西进行评估。还有,元函数版本怎么能这么快?它实际上并不比优化的机器代码慢多少。

最佳答案

A的模板版本中,当特定专业时,说 A<2,3> , 被实例化,编译器记住这个类型,并且永远不需要再次实例化它。这是因为类型是唯一的,对这个元函数的每次“调用”只是计算一个类型。consteval函数版本未针对此进行优化,因此 A(2,3)可能会被评估多次,具体取决于控制流,从而导致您观察到的性能差异。没有什么可以阻止编译器“缓存”函数调用的结果,但这些优化可能还没有实现。

关于c++ - 为什么 consteval/constexpr 和模板元函数之间的编译时间存在如此巨大的差异?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68371252/

相关文章:

c++ - 概念检查可以延迟到 C++ 中的类实例化吗?

c++ - const的值在内存中已更改,但在输出中未更改。

c++ - Visual Studio : Command line error D8016: '/Ox' and '/RTC' command-line options are incompatible

C++ 代码生成和模板特化

wpf - IsPressed 触发器(按钮)导致异常

c++ - 为什么这个模板规范不能引用?

c++ - 单一来源项目结构的缺点是什么?

c++ - 为什么不能用兼容类型的 std::tuple 按元素构造 std::tuple?

c++ - 不能在 C++20 中将 std::cin 与 char* 或 char[] 一起使用

C++ 对不同概念的不同使用声明