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

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

我很好奇就编译时评估而言我可以将 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) 可能会被评估多次,具体取决于控制流,从而导致您观察到的性能差异。没有什么可以阻止编译器“缓存”函数调用的结果,但这些优化可能还没有实现。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?
Java在半透明框架/面板/组件上重新绘画。
Java“ Class.forName()”和“ Class.forName()。newInstance()”之间有什么区别?
在此环境中不提供编译器。也许是在JRE而不是JDK上运行?
Java用相同的方法在一个类中实现两个接口。哪种接口方法被覆盖?
Java 什么是Runtime.getRuntime()。totalMemory()和freeMemory()?
java.library.path中的java.lang.UnsatisfiedLinkError否*****。dll
JavaFX“位置是必需的。” 即使在同一包装中
Java 导入两个具有相同名称的类。怎么处理?
Java 是否应该在HttpServletResponse.getOutputStream()/。getWriter()上调用.close()?
Java RegEx元字符(。)和普通点?