【问题标题】:Why does tail call optimization need an op code?为什么尾调用优化需要操作码?
【发布时间】:2015-01-18 12:14:45
【问题描述】:

所以I've read many times before 从技术上讲,.NET 确实支持尾调用优化 (TCO),因为它有操作码,只是 C# 不生成它。

我不确定为什么 TCO 需要操作码或它会做什么。据我所知,能够做到 TCO 的要求是递归调用的结果不与当前函数范围内的任何变量相结合。如果您没有,那么我看不到操作码如何阻止您必须保持堆栈帧打开。如果你确实有,那么编译器不能总是很容易地将它编译成迭代的东西吗?

那么操作码的意义何在?显然,我缺少一些东西。在 TCO 完全可能的情况下,难道不能总是在编译器级别而不是操作码级别处理它吗?什么是不能的例子?

【问题讨论】:

  • 我没有受过教育的猜测是,这个操作码更像是从高级编译器到 JITter 的提示,因此后者 - 将快速编译作为其关键特性之一 - 是从分析 IL 以查看尾调用优化在任何给定场景中是否实际可行的潜在耗时任务中解脱出来。

标签: c# .net theory cil tail-call-optimization


【解决方案1】:

按照您已经提供的链接,这是我认为的部分,非常接近地回答了您的问题..


Source

CLR 和尾调用

当您处理由 CLR 管理的语言时,有两种编译器在起作用。有从你的语言源代码到 IL 的编译器(C# 开发人员将其称为 csc.exe),然后有从 IL 到本机代码的编译器(在运行时调用的 JIT 32/64 位编译器或 NGEN 时间)。 source->IL 和 IL->native 编译器都理解尾调用优化。 但是 IL->native 编译器——我将简称为 JIT——有最终决定权是否最终会使用尾调用优化。 source->IL 编译器可以帮助生成有利于进行尾调用的IL,包括使用“尾”。 IL 前缀(稍后会详细介绍)。 这样,source->IL 编译器可以构造它生成的 IL,以说服 JIT 进行尾调用。 但 JIT 总是可以为所欲为。

JIT 何时进行尾调用?

我问过我隔壁的邻居 Fei Chen 和 Grant Richins,他们恰好在 JIT 上工作,各种 JIT 在什么条件下会采用尾调用优化。完整的答案相当详细。快速总结是,JIT 尽可能使用尾调用优化,但不能使用尾调用优化的原因有很多。 不能选择尾随的一些原因:

  • 调用者在调用后没有立即返回 (duh :-))
  • 调用者和被调用者之间的堆栈参数不兼容,需要在被调用者执行之前在调用者的框架中移动内容
  • 调用者和被调用者返回不同的类型
  • 我们改为内联调用(内联比尾调用更好,并为更多优化打开了大门)
  • 安全阻碍
  • 调试器/分析器关闭了 JIT 优化

在您的问题中,最有趣的部分是上述安全性的示例,在我看来,这在我看来非常清楚。

.NET 中的安全性在许多情况下取决于堆栈是否准确...在运行时..这就是为什么,如上所述,责任由两个来源分担给CIL编译器和(运行时)CIL-to-native JIT 编译器,最终决定权归于后者。

【讨论】:

  • 但是为什么需要尾部操作码呢? JIT 总是可以尝试 TCO。
【解决方案2】:

猜想:在像 x86 汇编器这样的简单语言中,您可以“手动”管理堆栈,您不需要操作码 - 您只需适当地设置调用堆栈即可。

但是在像 .NET CIL 这样的更高级别中,堆栈是为您部分管理的,调用函数的整个行为是单个操作码(例如调用)。所以你需要一个不同的操作码来实现 TCO——一个“将控制流传递给这个函数,但不创建新的堆栈帧”的操作码。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-05-08
    • 2022-12-22
    • 2012-06-03
    • 2012-01-04
    • 1970-01-01
    • 2017-05-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多