Compiler construction 堆栈跟踪与TCO:非尾部递归语言通常具有高效的堆栈框架吗?

Compiler construction 堆栈跟踪与TCO:非尾部递归语言通常具有高效的堆栈框架吗?,compiler-construction,compiler-optimization,Compiler Construction,Compiler Optimization,我听说某些语言不支持尾部调用优化(TCO)的一个原因是,如果/当调试需要查看堆栈跟踪时。(我听过其他原因,比如“虚拟机不支持它”,但现在我们暂且不考虑Java。) 因此,对于某些语言中可能存在TCO但不执行TCO的某些情况,堆栈帧的唯一目的似乎是为生成的任何最终堆栈跟踪维护元数据。也就是说,堆栈帧可以是最小的,只包含足够的信息来生成堆栈跟踪 问题:最小化这些堆栈帧的大小是否有意义?它会不会最小化堆栈空间的使用,从而在耗尽空间之前允许更深层次的递归?这种想法是否适用于语言?(我特别想到了Pytho

我听说某些语言不支持尾部调用优化(TCO)的一个原因是,如果/当调试需要查看堆栈跟踪时。(我听过其他原因,比如“虚拟机不支持它”,但现在我们暂且不考虑Java。)

因此,对于某些语言中可能存在TCO但不执行TCO的某些情况,堆栈帧的唯一目的似乎是为生成的任何最终堆栈跟踪维护元数据。也就是说,堆栈帧可以是最小的,只包含足够的信息来生成堆栈跟踪

问题:最小化这些堆栈帧的大小是否有意义?它会不会最小化堆栈空间的使用,从而在耗尽空间之前允许更深层次的递归?这种想法是否适用于语言?(我特别想到了Python。)或者这是在实际节省空间方面所做的努力吗?(我认为,与通常在堆栈帧中的情况相比,生成漂亮堆栈跟踪所需的元数据实际上要多得多。)

简而言之:最小化堆栈帧的大小,作为TCO的替代方案


我的想法不是基于任何实际的基准。我可能离这里很远。

如果其中一个正在调试,则并非所有优化都需要打开,除非您正在调试优化本身。因此,尾部递归优化(TRO)不必在调试期间禁用回溯,除非开发环境是死板的(例如,没有“禁用优化”选项)

如果您记录了元数据,那么元数据占用的空间非常小;基本上,它说,“进行了递归调用”。一个包含少量字节元素的链表就可以做到这一点。但我认为在TRO面前记录回溯元数据是没有意义的;如果您正在进行优化,您可能也不想支付记录元数据的额外时间成本,因为有人可能希望稍后进行调试

我不知道你说的“最小化堆栈帧的大小”是什么意思,因为你是在TRO的上下文中问的。一般来说,一个好的编译器只需分配足够的空间来完成堆栈帧实例所表示的(子)例程的整个计算,就可以通过自动最小化大小。一个标准的优化是让具有非重叠生存期的变量在堆栈框架中共享相同的空间。我们可以通过两种不同的方式来实现这一点:a)子例程主体内不重叠的嵌套作用域可以在类似堆栈的规程中轻松共享它们的空间,b)将“堆栈帧”视为存储溢出值的一组寄存器,而不是堆栈;寄存器着色算法可以很好地处理这个问题


有时,操作系统(缺乏)支持会破坏堆栈帧最小化。有关Windows陷阱如何显著损坏堆栈帧大小的讨论,请参阅。

您基本上可以在“优化”尾部递归或维护“精确”调用堆栈之间进行选择。您要么维护某种“面包屑”,不完全优化尾部递归,要么在丢失堆栈跟踪的同时优化尾部递归

每个设计良好的语言实现(对运行时性能和“占用空间”感兴趣)都“最小化”堆栈帧大小


但是,就像计算机中几乎所有的东西一样,在相互竞争的因素之间存在着权衡——性能、“占地面积”、实现的简单性/可靠性、灵活性(例如,多种语言)、可调试性等等。

+1这是一个非常有趣的链接。我觉得很奇怪,Windows会将CPU上下文推到用户模式堆栈上,但是——它不应该推到内核模式堆栈上吗?如果堆栈指针坏了怎么办?系统会崩溃吗?@Mehrdad:完整的进程上下文存储在用户空间中是有道理的,特别是因为可以编写用户陷阱例程来检查/修改/重启陷阱。(在我们的PARLANSE语言中,我们这样做是为了除零陷阱等)。愚蠢的是将其推到用户堆栈中;我的观点是,应用程序可以告诉操作系统在哪里放置陷阱数据(包括用于向后兼容的堆栈),以避免对堆栈帧大小的这种不合理的要求@迈赫达德:。。。不管怎样,如果SP坏了并且出现陷阱,那么硬件可能会产生“双重故障”,Windws只会中止您的进程。如果SP可以接收硬件推送的陷阱数据,但没有足够的空间来容纳MS推送的额外内容,那么Windows可能会崩溃;想必MS考虑过(或经历过)这一点,并且有一个辩护理由。谢谢你的回答。我曾认为,关闭堆栈跟踪并因此启用TCO/TRO可能会使一些依赖递归的应用程序在启用调试的情况下很难运行,而不会耗尽堆栈内存。感谢您指出,完整的上下文不一定被推到堆栈中,而只是静态分析过程中认为必要的部分。O(n)空间就是O(n)空间。无论正常数有多小,它仍然是O(n),所以最小化堆栈帧没有帮助。要么你有尾部调用优化,要么你没有;就我所知,所有语言都有“有效的堆栈框架”。它们包含必须包含的内容:局部变量和返回地址。我不知道如何进一步“最小化”。@EJP:你没有仔细阅读我的答案。编译器可以做很多事情来最小化堆栈帧占用的空间量,包括做好寄存器分配(而不是糟糕的寄存器分配,因此需要大量溢出时间)和值生存期管理。