C# 不发出“尾巴”是否存在技术原因。 CIL指令? [复制]

2024-01-27

可能的重复:
为什么.net/C# 不消除尾递归? https://stackoverflow.com/questions/491376/why-doesnt-net-c-eliminate-tail-recursion

采取以下 C# 代码:

using System;

namespace TailTest
{
    class MainClass
    {
        public static void Main (string[] args)
        {
            Counter(0);
        }

        static void Counter(int i)
        {
            Console.WriteLine(i);
            if (i < int.MaxValue) Counter(++i);
        }
    }
}

C# 编译器(无论如何)会将 Counter 方法编译为以下 CIL:

.method private static hidebysig default void Counter (int32 i) cil managed 
{
.maxstack 8
IL_0000:  ldarg.0 
IL_0001:  call void class [mscorlib]System.Console::WriteLine(int32)
IL_0006:  ldarg.0 
IL_0007:  ldc.i4 2147483647
IL_000c:  bge IL_0019
IL_0011:  ldarg.0 
IL_0012:  ldc.i4.1 
IL_0013:  add 
IL_0014:  call void class TailTest.MainClass::Counter(int32)
IL_0019:  ret 
}

上面代码的问题是它会导致堆栈溢出(在我的硬件上大约是 i=262000)。为了解决这个问题,一些语言采取了所谓的尾部调用消除或尾部调用优化(TCO)的方法。本质上,它们将递归调用变成了循环。

我的理解是 .NET 4 JIT 的 64 位实现现在执行 TCO 并避免像上面的 CIL 那样的代码溢出。但是,32 位 JIT 则不然。 Mono 似乎也没有。有趣的是,JIT(面临时间和资源压力)会执行 TCO,而 C# 编译器则不会。我的问题是为什么 C# 编译器本身不更了解 TCO?

有一条 CIL 指令告诉 JIT 执行 TCO。例如,C# 编译器可以生成以下 CIL:

.method private static hidebysig default void Counter (int32 i) cil managed 
{
.maxstack 8
IL_0000:  ldarg.0 
IL_0001:  call void class [mscorlib]System.Console::WriteLine(int32)
IL_0006:  ldarg.0 
IL_0007:  ldc.i4 2147483647
IL_000c:  bge IL_001c
IL_0011:  ldarg.0 
IL_0012:  ldc.i4.1 
IL_0013:  add 
IL_0014:  tail.
IL_0017:  call void class TailTest.MainClass::Counter(int32)
IL_001c:  ret 
}

与原始代码不同,此代码不会溢出,即使在 32 位 JIT(.NET 和 Mono)上也能运行完成。神奇之处在于tail.CIL指令。像 F# 这样的编译器将自动生成包含该指令的 CIL。

所以我的问题是,C# 编译器不这样做是否有技术原因?

我知道从历史上看这可能是不值得的。代码如下Counter()在惯用的 C# 和/或 .NET 框架中并不常见。您可以轻松地将 C# 的 TCO 视为不必要的或过早的优化。

随着 LINQ 和其他东西的引入,C# 和 C# 开发人员似乎都在向功能性更强的方向发展。因此,如果使用递归不是一件不安全的事情,那就太好了。但我的问题确实是一个更具技术性的问题。

我错过了一些东西,使得这样的 TCO 对于 C# 来说是一个坏主意(或者是一个有风险的主意)。或者是否有什么事情让它变得特别棘手?这确实是我希望了解的。有什么见解吗?

EDIT:感谢您提供的宝贵信息。我只是想澄清,我并不是在批评这项功能的缺乏,甚至不是要求这项功能。我对优先级的合理性不太感兴趣。我的好奇心是我可能无法察觉或理解哪些障碍,使这成为一件困难、危险或不受欢迎的事情。

也许不同的背景将有助于集中对话......

假设我要在 CLR 上实现我自己的类似 C# 的语言。为什么我不(除了机会成本)包括自动和透明的“尾部”排放。在适当的地方进行指导?在使用类似于 C# 的语言支持此功能时,我会遇到哪些挑战或会引入哪些限制。

再次(提前)感谢您的回复。


检查以下链接

为什么 .NET/C# 不优化尾调用递归? https://stackoverflow.com/questions/491376/why-doesnt-net-c-eliminate-tail-recursion/491463#491463
http://social.msdn.microsoft.com/Forums/en-US/netfxtoolsdev/thread/67b6d908-8811-430f-bc84-0081f4393336?StatusCode=1 http://social.msdn.microsoft.com/Forums/en-US/netfxtoolsdev/thread/67b6d908-8811-430f-bc84-0081f4393336?StatusCode=1

以下声明是MS官方(Luke Hoban Visual C# Compiler Program Manager)并从上一个链接复制的

谢谢你的建议。我们考虑过在某个数字处发出尾部调用指令 C# 编译器开发中的要点。然而,还有一些微妙的问题 到目前为止,这促使我们避免这种情况:1)实际上存在不小的开销 在 CLR 中使用 .tail 指令的成本(它不仅仅是一个跳转指令,如 尾部调用最终会出现在许多不太严格的环境中,例如功能性环境 尾部调用经过大量优化的语言运行时环境)。 2)有 很少有真正的 C# 方法可以合法地发出尾部调用(其他语言 鼓励具有更多尾递归的编码模式,以及许多严重依赖 在尾部调用优化上实际上做了全局重写(例如Continuation Passing 转换)以增加尾递归的数量)。 3) 部分原因是 2), 由于本应成功的深度递归而导致 C# 方法堆栈溢出的情况 是相当罕见的。

话虽如此,我们会继续关注这一点,并且我们可能会在未来的版本中 编译器找到一些有意义的模式来发出 .tail 指令。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C# 不发出“尾巴”是否存在技术原因。 CIL指令? [复制] 的相关文章

随机推荐

  • JavaScript 文件加载顺序和依赖关系管理

    只是想知道这个 我有几个单独的 javascript 文件 它们都包含基于模块模式的代码 一些模块具有其他一些模块作为依赖项 如果我知道在加载页面之前不会在 HTML 上调用任何代码 那么文件加载的顺序仍然重要吗 模块代码位于立即函数内的事
  • popen 中整个字符串命令和字符串列表之间的区别

    我发现大多数程序员建议使用字符串列表来表示 popen 中的命令 然而 在我自己的项目中 我发现整个字符串在更多情况下都有效 例如下面的作品 subprocess Popen pgrep f run stdout subprocess PI
  • C ++生成(xyz)范围内的点

    有没有比这更好的方法来生成这样的点列表 就图书馆而言 我对任何基于特征的方法持开放态度 auto it voxels begin for auto i 180 i lt 90 i for auto j 80 j lt 70 j for au
  • 正确使用$@

    我正在尝试编写一个小脚本 它接受任意数量的命令行参数 打印出rwx文件 不是目录 的权限 我拥有的是 file if f file then ls l file fi 然而 这仅接受一个命令行参数 谢谢你的帮助 这是之间的一些差异的演示 a
  • 如何在Pubnub中的ngHistory中实现回调?

    当尝试检索历史消息时on事件 加载时间太长 微调器显示和隐藏速度太快 但该消息尚未加载 我们如何计算或获取加载历史记录的准确时间 scope limit 100 PubNub ngHistory channel scope channel
  • 设置开发服务器

    开发 PHP 应用程序时 最好有一个用于开发 测试的服务器 然后是一个实时服务器 一旦准备好就可以放置所有内容 好吧 但是怎么办呢 如果您通过托管公司进行托管 如何设置自己的开发服务器来测试模仿所有 LAMP 设置作为实时服务器 因为如果它
  • 如何对 GUI 进行单元测试?

    我的代码中的计算经过了充分测试 但由于 GUI 代码太多 我的整体代码覆盖率低于我的预期 有关于单元测试 GUI 代码的指南吗 这还有道理吗 例如 我的应用程序中有图表 我一直无法弄清楚如何自动测试图表 AFAIK 需要人眼来检查图表是否正
  • Corda 在终端中查看消耗状态

    有没有一种简单的方法可以使用 CordaRPCOps 界面查看终端中的消耗状态 似乎VaultQuery默认返回未使用的状态 我不知道如何使用vaultQueryBy或任何符合条件的东西 我知道应该有消耗状态 因为我可以用 H2 看到它们
  • 为什么我的 datumTokenizer 从未被调用?

    我在 datumTokenizer 函数中放置了一个断点 但它似乎从未被调用 为什么不 它适用于仅远程数据吗 var engine new Bloodhound datumTokenizer function d return Bloodh
  • es6箭头函数调试器语句

    如果我有这样的功能 param gt params 1 我需要放一个debugger函数体内的语句 添加括号是这样的 param gt debugger return params 1 唯一的选择 来自MDN https developer
  • 内联安装 Chrome 扩展程序时出现“未捕获无效的 Chrome Web Store 项目 URL”

    我创建了一个 Chrome 扩展程序 并将其发布到 Chrome Webstore 上的测试人员组 当我尝试从我的网站加载 添加 扩展程序时 出现错误 未捕获无效的 Chrome 应用商店项目 URL 论方法Installer protot
  • Android 中的 CTL(复杂文本语言)支持

    我正在尝试为亚洲语言开发 Android IME 需要复杂的渲染 例如更改字形形式 重新排序字符顺序等 在PC中 使用GTK Pango Graphite就足够了 在 Android 中 如何解决复杂文本语言的 Unicode 渲染问题 提
  • ARKit:再现Project Point功能

    我正在尝试重现 ARCamera 的项目点函数 但由于某种原因 这些值没有正确匹配 我采用 ARCamera 的投影矩阵和视图矩阵 并应用基本的 CG 透视变换数学 PV p 但 NDC 值与 ARCamera 的项目点函数给出的像素值不匹
  • 如何从 SSMS 获取完整结果集

    如何获取 SQL Server Management Studio 中行的完整内容 如果您使用 结果网格 数据将被编码 因此换行符之类的内容会丢失 如果您执行 结果到文件 或 结果到文本 则文本限制为 8192 个字符 注意 我有解决方案
  • 设置为联系人铃声?安卓

    我正在尝试学习如何添加设置为联系人铃声功能 我已经知道如何设置默认铃声 但我不知道如何设置为联系人铃声 我到达了选择联系人的部分 但我不知道如何为该联系人分配铃声 这部分困扰着我 我似乎无法在已经提出的有关该主题的问题中找到答案 到目前为止
  • 执行 ajax 调用时序列化某个类型的对象时检测到循环引用

    在我看来 我使用的是 Viewmodel 并且我有一个表单 该表单只有一个接受日期的文本框 不是视图模型的一部分 和 3 个表 默认情况下 在页面加载时 表格中填充了基于今天日期的数据 您可以在下面的控制器代码中看到 但是如果用户选择日期并
  • 如何让机器在执行期间停止睡眠/休眠

    我有一个用 golang 编写的应用程序 部分 作为其操作的一部分 它将生成一个外部进程 用 c 编写 并开始监视 这个外部过程可能需要几个小时才能完成 因此我正在寻找一种方法来防止机器在处理时休眠或休眠 我希望能够放弃这个锁 以便当进程完
  • 有没有适用于 Windows 的开源 C 可视化调试器? [关闭]

    Closed 此问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 有没有适用于 Windows 的开源 C 可视化调试器 我听说过 Visual C Express 免费
  • JavaScript 中的谷歌 API

    我正在尝试使用 javascript 从 google 获取日历信息 我读过 如何做 手册 他们没有帮助 即使这个 有用的 复制粘贴代码 授权 也没有 有人能教我如何使用 google api 吗 也许有人有一些样本可以分享 还有这个漂亮的
  • C# 不发出“尾巴”是否存在技术原因。 CIL指令? [复制]

    这个问题在这里已经有答案了 可能的重复 为什么 net C 不消除尾递归 https stackoverflow com questions 491376 why doesnt net c eliminate tail recursion