我正计划编写一种针对 .NET 平台的编程语言,这让我开始思考针对此类平台的代码生成方面。我是编写编译器的新手,但我知道优化是编译的阶段之一(或者可以有)。我开始怀疑花时间优化输出有什么好处(在这种情况下是 CIL,但这也适用于 JVM),因为 JIT 编译器和 JVM 的 HotSpot 之类的东西可以在运行时进行优化。在针对 .NET 或 JVM 时优化生成的代码(CIL 或 JVM 等效代码)是否有任何好处,因为 JIT 已经进行了优化?
3 回答
这取决于。有无数的优化。任何给定的编译器(您的编译器、JIT 编译器或任何其他编译器)都必须只实现其中的一个子集。此选择取决于可用时间、典型/预期输入代码、优先级等,因此构建 JIT 编译器的工程师可能选择了对他们期望的程序运行良好的优化,但对您所期望的程序类型不太适用关心。
您必须确定 JIT 编译器遗漏了哪些优化。这样做的方法当然是经验性的:实际编写程序,让 JIT 编译器优化它们(确保正确执行这部分 - 禁用调试、编译发布、选择实际基准等),然后检查最终机器码。寻找意外的代码(当然,您需要为此具备汇编知识)并确定它是否是错过的优化,或者 JIT 是否比您想象的更智能。
如果是错过了优化,你还有另一个问题:你不能输出你想要的机器码,你必须生成不同的IL来代替。错过的优化可能是由于 VM 不知道的语言特性(例如 JVM 上的多方法)。您在编译期间将其降低为 VM 的术语,但您选择的翻译与 JIT 的传递顺序、启发式等不相符。由于您不能自己输出机器代码,您现在必须找到一个替代的 IL 片段相同的输入语言代码。理想情况下,JIT 编译器可以很好地处理。发现这可能是一种想象力的练习,但这在技术上并不难,只是与基准测试交织在一起的猜测。
正如另一个答案指出的那样,JIT 编译器在时间限制下工作。这可能会导致错过可能发生的优化(例如,持续传播超时),但是由于 JIT 编译器的创建者面临同样的问题,如果您不创建更大/更多的内容,这可能不会太严重复杂的代码。 如果您创建了如此糟糕的代码,以至于 JIT 编译器无法全部修复,那么您必须在 AOT 编译器中复制其优化。我不相信这是一种可能的情况,即使发生这种情况,即使是非常简单的优化也应该主要解决问题。
所以,总而言之:从简单的翻译开始,然后找出错过的优化,或者让 JIT 编译器更容易优化,或者自己做(如果可能的话——自适应优化在 AOT 设置中要困难得多)。
我认为这个问题一般很难回答。
例如,F# 编译器执行尾调用优化,因为尾递归函数在该语言中很常见,因此 F# 编译器在某些情况下可以比 JIT 编译器更好地优化它们,而某些版本的 JIT 编译器没有根本不执行优化。
因此,您的语言可能有一些常见的操作,其简单的实现不会很好地执行。在这种情况下,发出经过优化的 IL 代码是有意义的。
我认为你应该做的和你写一个普通程序时一样:首先以一种简单易读的方式编写你的代码。只有当某些东西表现不佳时,才尝试对其进行优化。可能值得考虑的是,您将来可能需要一些优化并使您的代码足够模块化,这样您就不必因为一些优化而重写其中的一半。但就目前而言,这应该足够了。
编写编译器已经是一项艰巨的工作(即使您的目标是 IL)。先完成它,然后再考虑优化。
通常,JIT 编译器有一些阈值来控制它们将尝试执行多少优化。这些可能基于方法的 IL 的大小和/或 JIT 编译该方法所花费的时间。所以是的,已经优化的 IL可能会从进一步的 JIT 优化中受益。与往常一样,需要进行权衡:您希望花多少时间向编译器添加 AOT 优化(以及测试/维护它们)与您的代码可以多快 JIT 编译,以及优化级别。
改进的幅度在很大程度上取决于 AOT 优化的 IL 相对于未优化的 IL 的简单程度(和更小),以及管理 JIT 编译器的阈值(至少对于 Microsoft CLR 而言,这并不广为人知)。找出答案的唯一方法是自己做一些测试。