Runtime: 引入分层 JIT

创建于 2016-04-14  ·  63评论  ·  资料来源: dotnet/runtime

为什么 .NET JIT 没有分层?

JIT 有两个主要设计目标:快速启动时间和高稳态吞吐量。

起初,这些目标似乎不一致。 但是通过两层 JIT 设计,它们都是可以实现的:

  1. 所有代码都开始解释。 这导致启动时间极快(比 RyuJIT 更快)。 示例: Main方法几乎总是很冷,并且jitting它是浪费时间。
  2. 经常运行的代码是使用非常高质量的代码生成器来生成的。 很少有方法会很热(1%?)。 因此,高质量 JIT 的吞吐量并不重要。 它所花费的时间与 C 编译器生成非常好的代码所花费的时间一样多。 此外,它可以_假设_代码是热的。 它可以疯狂地内联和展开循环。 代码大小不是问题。

达到这种架构似乎并不太昂贵:

  1. 与 JIT 相比,编写解释器似乎便宜。
  2. 必须创建高质量的代码生成器。 这可能是 VC 或 LLILC 项目。
  3. 必须可以将解释的运行代码转换为编译的代码。 这个有可能; JVM 做到了。 它被称为堆栈替换(OSR)。

JIT团队是否正在追求这个想法?

.NET 在数以百万计的服务器上运行。 我觉得很多性能都被搁置了,由于代码生成不佳,数百万台服务器被客户浪费了。

类别:吞吐量
主题:大赌注
技能级别:专家
成本:超大

area-CodeGen-coreclr enhancement optimization tenet-performance

最有用的评论

通过最近的 PR(https://github.com/dotnet/coreclr/pull/17840、https://github.com/dotnet/sdk/pull/2201),您还可以将分层编译指定为运行时配置。 json 属性或 msbuild 项目属性。 使用此功能需要您使用最近的版本,而环境变量已经存在了一段时间。

所有63条评论

@GSPP分层是规划对话中的一个不变主题。 我的印象是,如果这能提供任何安慰,这只是_何时_,而不是_if_的问题。 至于_为什么_它还不存在,我认为这是因为,从历史上看,感知的潜在收益并不能证明管理多种代码生成模式增加的复杂性和风险所需的额外开发资源是合理的。 不过,我真的应该让专家来谈谈这个,所以我会添加他们。

/cc @dotnet/jit-contrib @russellhadley

不知何故,我怀疑这在 crossgen/ngen、Ready to Run 和 corert 的世界中是否仍然适用。

目前这些都不能提供高稳定状态吞吐量,这对大多数 Web 应用程序来说很重要。 如果他们这样做了,我对此很满意,因为我个人并不关心启动时间。

但到目前为止,.NET 的所有代码生成器都试图在这两个目标之间做出不可能的平衡,但都不能很好地实现。 让我们摆脱这种平衡行为,以便我们可以将优化设置为 11。

但到目前为止,.NET 的所有代码生成器都试图在这两个目标之间做出不可能的平衡,但都不能很好地实现。 让我们摆脱这种平衡行为,以便我们可以将优化设置为 11。

我同意,但解决这个问题不需要像口译员这样的东西。 只是一个好的 crossgen 编译器,无论是更好的 RyuJIT 还是 LLILC。

我认为最大的优势在于需要在运行时生成代码的应用程序。其中包括动态语言和服务器容器。

动态生成的代码确实是一种动机——但静态编译器永远无法访问运行时可用的所有信息也是事实。 不仅如此,即使它推测(例如基于配置文件信息),静态编译器在存在模态或外部上下文相关行为的情况下也很难做到这一点。

Web 应用程序不需要任何 ngen 风格的处理。 它不太适合部署管道。 生成大二进制文件需要花费大量时间(即使几乎所有代码都是动态死的或冷的)。

此外,在调试和测试 Web 应用程序时,您不能依赖 ngen 来提供逼真的性能。

此外,我第二卡罗尔使用动态信息的观点。 解释层可以分析代码(分支、循环行程计数、动态调度目标)。 这是一个完美的匹配! 首先收集配置文件,然后优化。

分层永远解决了每个场景中的所有问题。 粗略地说 :) 这实际上可以让我们实现 JIT 的承诺:实现超越 C 编译器所能做的性能。

RyuJIT 的当前实现对于第 1 层来说已经足够了......问题是:对于可以在事后运行的热路径进行第 2 层极端优化 JIT 是否有意义? 本质上,当我们检测到或有足够的运行时信息来知道某物很热或被要求从一开始就使用它时。

到目前为止,RyuJIT 足以成为第 1 层。问题在于解释器的启动时间会_远_快(据我估计)。 第二个问题是为了推进到第 2 层,执行第 1 层代码的本地状态必须可转移到新的第 2 层代码(OSR)。 这需要对 RyuJIT 进行更改。 我认为,添加解释器将是一条更便宜的路径,同时具有更好的启动延迟。

一个更便宜的变体是不使用第 2 层代码替换正在运行的代码。 相反,请等到第 1 层代码自然返回。 如果代码进入一个长时间运行的热循环,这可能是一个问题。 它永远不会以这种方式达到第 2 层性能。

我认为这不会太糟糕,可以用作 v1 策略。 可以使用缓解想法,例如将方法标记为热的属性(即使使用当前的 JIT 策略也应该存在)。

@GSPP是的,但这并不意味着您下次运行时不会知道。 如果 Jitted 代码和检测变得持久,那么第二次执行你仍然会得到第 2 层代码(以一些启动时间为代价)——这一次我个人并不关心,因为我主要编写服务器代码。

与 JIT 相比,编写解释器似乎便宜。

与其编写一个全新的解释器,不如在禁用优化的情况下运行 RyuJIT 是否有意义? 这会足够改善启动时间吗?

必须创建高质量的代码生成器。 这可能是 VC

您是在谈论 C2,即 Visual C++ 后端吗? 这不是跨平台的,也不是开源的。 我怀疑修复两者是否会很快发生。

禁用优化的好主意。 但是,OSR 问题仍然存在。 不确定生成允许运行时在安全点导出 IL 架构状态(本地和堆栈)的代码有多困难,将其复制到第 2 层 jitted 代码并恢复第 2 层执行中间功能。 JVM 做到了,但谁知道实现它需要多少时间。

是的,我说的是C2。 我想我记得至少有一个桌面 JIT 是基于 C2 代码的。 可能不适用于 CoreCLR,但可能不适用于桌面。 我确信微软有兴趣拥有对齐的代码库,所以这可能确实已经过时了。 LLVM 似乎是一个不错的选择。 我相信目前有多种语言有兴趣使 LLVM 与 GC 和托管运行时一起工作。

LLVM 似乎是一个不错的选择。 我相信目前有多种语言有兴趣使 LLVM 与 GC 和托管运行时一起工作。

关于这个主题的一篇有趣的文章:Apple 最近将其 JavaScript JIT 的最后一层从 LLVM 中移出: https ://webkit.org/blog/5852/introducing-the-b3-jit-compiler/。 我们可能会遇到与他们遇到的类似的问题:编译时间慢和 LLVM 缺乏对源语言的了解。

对于第二层来说,比 RyuJIT 慢 10 倍是完全可以接受的。

我不认为缺乏源语言知识(这是一个真正的问题)是 LLVM 架构中固有的。 我相信多个团队正忙于将 LLVM 转移到可以更轻松地利用源语言知识的状态。 _所有_非 C 高级语言在 LLVM 上编译时都有这个问题。

WebKIT FTL/B3 项目比 .NET 更难成功,因为它们必须在运行代码时表现出色,_intotal_ 消耗几百毫秒的时间然后退出。 这是驱动网页的 JavaScript 工作负载的本质。 .NET 不在那个位置。

@GSPP我相信您可能知道LLILC 。如果没有,请看一下。

我们一直致力于 LLVM 对 CLR 概念的支持,并投资于 EH 和 GC 改进。两者还有很多工作要做。除此之外,还有一些未知的工作量使优化在 GC 存在下正常工作。

LLILC 似乎停滞不前。 是吗?
2016 年 4 月 18 日晚上 7:32,“Andy Ayers” [email protected]写道:

@GSPP https://github.com/GSPP我相信你可能知道 LLILC
https://github.com/dotnet/llilc。 如果没有,请看一下。

我们已经为 CLR 概念的 LLVM 支持工作了一段时间,并且已经
投资于 EH 和 GC 改进。 还有很多事情要做
两个都。 除此之外,还有一些未知的工作量得到优化
在有 GC 的情况下正常工作。


您收到此消息是因为您发表了评论。
直接回复此邮件或在 GitHub 上查看
https://github.com/dotnet/coreclr/issues/4331#issuecomment -211630483

@drbo - LLILC 目前处于次要地位 - MS 团队一直专注于在 RyuJIT 中提出更多目标,并修复 CoreCLR 驱动发布时出现的问题,这几乎花费了我们所有的时间。 根据我们(目前)在 LLILC 上的进展情况,我的 TODO 列表(在我丰富的空闲时间)上写了一篇经验教训帖子,但我还没有完成。
在分层方面,这个话题多年来引起了很多讨论。 我认为考虑到一些新的工作负载,以及新增的可版本化的准备运行映像,我们将重新审视如何以及在何处进行分层。

@russellhadley你有空闲时间写这篇文章吗?

我假设,应该有一些关于未提升的堆栈槽和 gcroots 破坏优化和缓慢的 jitting 时间......我最好看看项目的代码。

我还想知道直接跳入 SelectionDAG 并执行 LLVM 后端的一部分是否可能且有利可图。 至少有一些窥视孔和复制传播......如果 LLILC 支持对寄存器的 gcroot 提升

我很好奇 LLILC 的状态,包括当前的瓶颈以及它对 RyuJIT 的影响。 LLVM 作为成熟的“工业级”编译器应该有大量可供 OSS 使用的优化。 在邮件列表中已经有一些关于更高效、更快的位码格式序列化/反序列化的讨论; 我想知道这对 LLILC 是否有用。

有没有更多的想法? @russellhadley CoreCLR 已经发布,RyuJIT 已经移植到(至少)x86——路线图上的下一步是什么?

有关这方面工作的开始,请参阅 dotnet/coreclr#10478。

还有 dotnet/coreclr#12193

@noahfalk ,您能否提供一种方法来告诉运行时立即从托管代码本身强制进行第 2 层编译? 对于大多数用例来说,分层编译是一个非常好的主意,但我正在从事一个与启动时间无关但吞吐量稳定延迟至关重要的项目。

在我的脑海中,这可能是:

  • 配置文件中的一个新设置,一个像<gcServer enabled="true" />这样的开关来强制 JIT 总是跳过第 1 层
  • 或类似RuntimeHelpers.PrepareMethod之类的东西,它将由作为热路径一部分的所有方法的代码调用(我们使用它在启动时预先 JIT 我们的代码)。 这样做的好处是为应该知道什么是热路径的开发人员提供了更大的自由度。 这种方法的额外重载就可以了。

诚然,很少有项目会从中受益,但我有点担心 JIT 默认跳过优化,而且我无法告诉它我宁愿让它大量优化我的代码。

我知道您在设计文档中写了以下内容:

添加可从托管代码 API 访问的新构建管道阶段以执行自修改代码。

这听起来很有趣😁,但我不太确定它是否涵盖了我在这里问的内容。


还有一个相关的问题:第二次 JIT 通行证什么时候开始? 什么时候第n次调用一个方法? JIT 会发生在方法应该运行的线程上吗? 如果是这样,那将在方法调用之前引入延迟。 如果您实施更积极的优化,此延迟将比当前 JIT 时间更长,这可能会成为一个问题。

它应该在方法被调用足够多次时发生,或者如果一个循环
执行足够的迭代(舞台替换)。 它应该发生
在后台线程上异步。

2017 年 6 月 29 日晚上 7:01,“Lucas Trzesniewski” [email protected]
写道:

@noahfalk https://github.com/noahfalk ,你能提供一种方法吗
告诉运行时立即强制执行第 2 层编译
托管代码本身? 分层编译对大多数人来说是一个非常好的主意
用例,但我正在做一个与启动时间无关的项目
但是吞吐量稳定的延迟是必不可少的。

在我的脑海中,这可能是:

  • 配置文件中的新设置,类似的开关 enabled="true" /> 强制 JIT 始终跳过第 1 层
  • 或类似 RuntimeHelpers.PrepareMethod 的东西,这将是
    由作为热路径一部分的所有方法的代码调用(我们是
    使用它在启动时预 JIT 我们的代码)。 这样做的好处是
    为应该知道什么的开发人员提供更大的自由度
    热门路径是。 这种方法的额外重载就可以了。

当然,很少有项目会从中受益,但我有点担心
默认情况下 JIT 跳过优化,我无法告诉它
我宁愿让它大量优化我的代码。

我知道您在设计文档中写了以下内容:

添加可从托管代码 API 访问的新构建管道阶段
自修改代码。

这听起来很有趣😁,但我不太确定它涵盖了什么

我在这里问。

还有一个相关的问题:第二次 JIT 通行证什么时候开始? 当一个
方法将被第n次调用? JIT 会发生在
该方法应该运行的线程? 如果是这样,那将引入一个
方法调用前的延迟。 如果你实施更激进
优化此延迟将比当前 JIT 时间更长,这
可能会成为一个问题。


你收到这个是因为你被提到了。
直接回复此邮件,在 GitHub 上查看
https://github.com/dotnet/coreclr/issues/4331#issuecomment-312130920
或使线程静音
https://github.com/notifications/unsubscribe-auth/AGGWB2WbZ2qVBjRIQWS86MStTSa1ODfoks5sJCzOgaJpZM4IHWs8
.

@ltrzesniewski - 感谢您的反馈! 当然,我希望分层编译对绝大多数项目有用,但权衡可能并不适合每个项目。 我一直在猜测我们会保留一个环境变量来禁用分层抖动,在这种情况下,您可以保持现在具有更高质量(但生成速度较慢)的运行时行为。 为您的应用设置环境变量是否合理? 其他选项也是可能的,我只是倾向于环境变量,因为它是我们可以使用的最简单的配置选项之一。

还有一个相关的问题:第二次 JIT 通行证什么时候开始?

这是一项很可能会随着时间而演变的政策。 当前的原型实现使用了一个简单的策略:“方法是否被调用 >= 30 次”
https://github.com/dotnet/coreclr/blob/master/src/vm/tieredcompilation.cpp#L89
https://github.com/dotnet/coreclr/blob/master/src/vm/tieredcompilation.cpp#L122

方便的是,这个非常简单的策略表明我的机器有一个很好的性能改进,即使这只是一个猜测。 为了创建更好的策略,我们需要获得一些真实世界的使用反馈,而获得这些反馈需要核心机制在各种场景中都相当稳健。 所以我的计划是先提高健壮性/兼容性,然后对调优策略做更多的探索。

@DemiMarie - 我们现在没有任何东西可以跟踪循环迭代作为政策的一部分,但它对未来的前景很有趣。

是否对分析、推测优化和
去优化? JVM 完成所有这些工作。

2017 年 6 月 29 日晚上 8:58,“Noah Falk” [email protected]写道:

@ltrzesniewski https://github.com/ltrzesniewski - 感谢
回馈! 当然,我希望分层编译对广大
大多数项目,但权衡可能并不适合每个项目。
我一直在猜测我们会留下一个环境变量来
禁用分层抖动,在这种情况下,您保留运行时行为
现在有更高质量(但生成速度较慢)的 jitting。 是
设置环境变量对您的应用程序来说是合理的吗?
其他选择也是可能的,我只是倾向于环境
变量,因为它是我们可以使用的最简单的配置选项之一。

还有一个相关的问题:第二次 JIT 通行证什么时候开始?

这是一项很可能会随着时间而演变的政策。 目前的
原型实现使用了一个简单的策略:“方法是否已经
调用 >= 30 次”
https://github.com/dotnet/coreclr/blob/master/src/vm/
分层编译.cpp#L89
https://github.com/dotnet/coreclr/blob/master/src/vm/
分层编译.cpp#L122

方便的是,这个非常简单的策略表明了一个很好的性能改进
我的机器,即使它只是一个猜测。 为了制定更好的政策
我们需要获得一些真实世界的使用反馈,并获得反馈
将要求核心机制在各种
情景。 所以我的计划是先提高健壮性/兼容性,然后再做
调整策略的更多探索。

@DemiMarie https://github.com/demimarie - 我们没有任何东西
现在跟踪循环迭代作为策略的一部分,但它很有趣
对未来的展望。


你收到这个是因为你被提到了。
直接回复此邮件,在 GitHub 上查看
https://github.com/dotnet/coreclr/issues/4331#issuecomment-312146470
或使线程静音
https://github.com/notifications/unsubscribe-auth/AGGWB5m2qCnOKJsaXFCFigI3J6Ql8PMQks5sJEgZgaJpZM4IHWs8
.

@noahfalk环境变量绝对不是允许您按应用程序控制此应用程序的解决方案。 对于服务器/服务应用程序,您通常不关心应用程序启动需要多少时间(我知道我们不会以牺牲性能为代价)。 开发一个数据库引擎,我可以直接告诉你,我们需要它从一开始就尽可能快地工作,甚至在潜在的新客户完成的普通路径或基准测试中也是如此。

另一方面,鉴于在典型环境中,正常运行时间可以一次以几周为单位来衡量,我们不在乎它是否需要 30 秒; 我们关心的是,强制用户发出一个通用开关(全有或全无),甚至让用户不得不关心它(如从配置文件中默认设置)是倒退 10 步。

不要误会我的意思,我期待的不仅仅是分层 JIT,因为它打开了高性能路径,需要尽可能多的时间来优化 JIT 级别的代码路径。 很久以前,我什至在与一些 JIT 工程师的非正式会谈中建议我自己,而你已经注意到了它。 但是,在应用程序范围(而不是系统范围)自定义行为的方法是(至少对我们而言)此特定功能的关键质量指标。

编辑:一些风格问题。

@redknightlois - 感谢您的跟进

环境变量绝对不是允许您按应用程序控制此应用程序的解决方案。

在这部分有点困惑......环境变量具有每个进程而不是每个系统的粒度,至少在我所知道的平台中。 例如,今天打开分层编译以仅在我运行的一个应用程序中进行测试:

set COMPLUS_EXPERIMENTAL_TieredCompilation=1
MyApp.exe
set COMPLUS_EXPERIMENTAL_TieredCompilation=0

我们关心的是 [我们不] 强迫用户……关心它

我认为您想要一个可以由应用程序开发人员指定的配置设置,而不是由运行应用程序的人指定? env var 的一种可能性是让应用程序用户启动一个启动 coreclr 应用程序的简单包装器(如批处理脚本),尽管我承认这似乎有点不雅。 我对替代方案持开放态度,而不是在 env var 上设置。 只是为了设定期望,这不是我在不久的将来会投入积极设计工作的领域,但我同意拥有适当的配置很重要。

还要提醒一下——假设我们继续沿着分层编译的道路走下去,我可以很容易地想象我们达到了这样一个点,即启用分层编译不仅是最快的启动,而且还超过了当前的稳态性能。 现在启动性能是我的目标,但它不是我们可以用它做的限制:)

是否对分析、推测优化和
去优化?

@DemiMarie - 他们肯定会在对话中出现,我认为很多人对分层编译打开了这些可能性感到兴奋。 仅就我自己而言,在将目光放得更高之前,我会努力专注于提供基础的分层编译功能。 我们社区中的其他人可能已经在其他应用程序上领先我。

@noahfalk是的,不优雅也意味着运行它的通常过程可能(并且很可能会)变得容易出错,这本质上是问题所在(完全确定没有人会搞砸的唯一方法是在系统范围内进行)。 我们知道它有效的另一种方法是,如果您要使用带有app.config条目的服务器 GC,您可以以相同的方式进行配置,您可以对分层编译执行相同的操作(至少在分层可以持续击败稳态性能)。 作为 JIT,您还可以使用assembly.config每个程序集执行此操作,并且如果也可以以这种方式选择其他旋钮,则可以提供当前不存在的功能。

环境变量通常是按用户或按系统设置的,这具有影响所有此类进程的潜在负面影响,跨多个运行时版本。 每个应用的配置文件似乎是一个更好的解决方案(即使每个用户/每个系统也可用)——类似于可以在 app.config 中设置的桌面配置值,但也可以使用环境变量或注册表.

我认为我们将实现最常见的路径,即每个应用程序。 系统范围的设置也可能有用,但我认为我们不必在功能实现之前考虑它。

请注意,尽管我们有一些想法,但我们还没有详细计算出二级 jit 应该为优化做什么。 它可能只是做 jit 今天所做的事情,但很可能会做得更多。

所以让我指出一些潜在的并发症......

第二层 jit 可能会在对第一层 jit 创建的代码行为的观察之上引导自己。 因此,绕过第一层 jit 并直接请求第二层 jit 可能根本不起作用,或者可能不起作用,就像让分层运行一样。 可能“分层绕过”选项,无论如何实施,最终都会提供类似于 jit 今天默认生成的代码,而不是第二层 jit 可以生成的代码。

第二层 jit 可能会以这样一种方式调整,即在大量方法上运行它会导致相对较慢的 jit 时间(因为我们的预期是相对较少的方法最终会与第二层 jit 一起运行,我们预计二层jit会做更彻底的优化)。 我们还不知道这里的正确权衡。

话虽如此...

我认为“积极优化”方法属性是有道理的——要求 jit 表现得有点像第二层 jit 可能会针对特定方法表现,并且可能在 prejitting 期间跳过这些方法(因为 prejitted 代码运行速度比 jitted 代码慢,特别是对于 R2R)。 但是将此概念应用于整个程序集或应用程序中的所有程序集似乎并不吸引人。

如果您将本机编译器中发生的情况作为一个合适的类比,则性能与编译时间/代码大小的权衡在更高的优化级别可能会变得非常糟糕,例如,编译时间延长 10 倍,性能总共提高 1-2%。 谜题的关键是知道哪些方法很重要,而做到这一点的唯一方法是让程序员知道或让系统自己弄清楚。

@AndyAyersMS我认为您在那儿一针见血。 JIT 处理“积极优化”属性可能会解决大多数问题,即在第一层 jit 没有时间提供反馈的情况下,无法获得足够的信息让 JIT 在隔离时生成更好的代码。

如果我们想要更多的分层, @redknightlois属性将不起作用:- T3 JIT、T4 JIT、...我不确定两个级别是否不够,但我们至少应该考虑这种可能性。

能够使用类似于MPGO的东西来开始运行第二层 jitted 代码会很棒。 快进第一层而不是完全绕过它。

@AndyAyersMS ,Azul 已经使用 LLVM 为 JVM 实现了托管 JIT,这是否使得将 LLVM 集成到 CLR 中变得更容易? 显然,在此过程中,更改已向上游推送到 LLVM。

仅供参考,我为我们需要做的一些特定工作创建了许多工作项,以实现分层 jitting (#12609, dotnet/coreclr#12610, dotnet/coreclr#12611, dotnet/coreclr#12612, dotnet/coreclr #12617)。 如果您的兴趣与其中之一直接相关,请随时向他们添加您的评论。 对于任何其他主题,我认为讨论将保留在这里,或者任何人都可以为特定的子主题创建一个问题,如果有足够的兴趣值得将其单独拆分出来。

@MendelMonteiro在抖动时提供 MPGO 样式的反馈数据当然是一种选择(目前我们只能在预抖动时读取此数据)。 可以检测的内容有各种限制,因此并非所有方法都可以以这种方式处理,还有其他限制我们需要查看(例如,没有可用于内联的反馈数据),创建所需的检测和训练运行MPGO 数据对许多用户来说是一个障碍,MPGO 数据可能与我们在启动第一层时所拥有的数据相匹配,也可能不匹配,但这个想法肯定有其优点。

就基于 LLVM 的上层而言——显然我们已经在某种程度上与 LLILC 进行了研究,当时我们经常与 Azul 人接触,所以我们熟悉他们在做的许多事情LLVM 使其更适合使用精确 GC 编译语言。

在 GC 和 EH 中,CLR 所需的 LLVM 支持与 Java 所需的 LLVM 支持存在(并且可能仍然存在)显着差异,以及必须对优化器施加的限制。 仅举一个例子:CLRs GC 目前不能容忍指向对象末尾的托管指针。 Java 通过基本/派生的配对报告机制来处理这个问题。 我们要么需要在 CLR 中支持这种配对报告,要么限制 LLVM 的优化器传递以从不创建这些类型的指针。 最重要的是,LLILC jit 很慢,我们不确定它最终会产生什么样的代码质量。

因此,弄清楚 LLILC 如何适应尚不存在的潜在多层方法似乎(现在仍然看起来)为时过早。 现在的想法是在框架中分层,并使用 RyuJit 作为第二层 jit。 随着我们了解更多,我们可能会发现确实有更高层次的 jit 的空间,或者,至少,更好地理解在这些事情变得有意义之前我们还需要做什么。

@AndyAyersMS也许您可以在 LLVM 中引入所需的更改,而不是解决它的限制。

多核 JIT及其配置文件优化是否与 coreclr 一起使用?

@benaadams - 是的,多核 JIT 有效。 我不记得默认启用它的(如果有的话)场景,但您可以通过配置将其打开: https ://github.com/dotnet/coreclr/blob/master/src/inc/clrconfigvalues.h#

我写了一个半玩具编译器,我注意到大多数时候,在相同的基础架构上可以很好地完成重击优化,而在更高层的优化器中可以完成的事情很少。

我的意思是:如果一个函数被多次命中,参数为:

  • 增加内联指令数
  • 使用更“高级”的寄存器分配器(类似 LLVM 的回溯着色器或全着色器)
  • 做更多的优化,也许是一些专业的本地知识。 例如:如果对象在方法中声明并且未在更大的内联函数的主体中分配,则允许将完整的对象分配替换为堆栈分配
  • 对于无法进行 CHA 的大多数命中对象,请使用 PIC。 例如,即使是 StringBuilder 也很可能不会被覆盖,如果代码每次被 StringBuilder 命中时都被标记为,内部调用的所有方法都可以安全地去虚拟化,并且在 SB 访问之前设置类型保护。

这也很好,但也许这是我清醒时的梦想,CompilerServices 提供“高级编译器”,以便能够通过代码或元数据访问,因此游戏或交易平台等地方可以通过启动受益提前编译哪些类和方法要“更深入地编译”。 这不是 NGen,但如果非分层编译器不一定是可能的(可取的),至少可以为需要这种额外性能的关键部分使用更重的优化代码。 当然,如果一个平台不提供大量优化(比如说 Mono),API 调用基本上是一个 NO-OP。

感谢@noahfalk@kouvel和其他人的辛勤工作,我们现在为分层奠定了坚实的基础。

我建议我们关闭这个问题并打开一个“我们如何使分层抖动更好”的问题。 我鼓励任何对该主题感兴趣的人尝试当前的分层,以了解当前的情况。 我们很乐意获得有关实际行为的反馈,无论好坏。

当前行为是否在某处描述? 我只发现了这一点,但更多的是关于实现细节而不是具体的分层。

我相信我们很快就会有一些总结性的文章,以及我们收集的一些数据。

可以通过设置COMPlus_TieredCompilation=1在 2.1 中启用分层。 如果您尝试过,请报告您的发现......

通过最近的 PR(https://github.com/dotnet/coreclr/pull/17840、https://github.com/dotnet/sdk/pull/2201),您还可以将分层编译指定为运行时配置。 json 属性或 msbuild 项目属性。 使用此功能需要您使用最近的版本,而环境变量已经存在了一段时间。

正如我们之前与@jkotas讨论的那样,分层 JIT 可以缩短启动时间。 当我们使用原生图像时它是否有效?
我们对 Tizen 手机上的几个应用程序进行了测量,结果如下:

系统 DLL|应用 DLL|分层|时间,秒
-----------|--------|------|--------
R2R |R2R |无 |2.68
R2R |R2R |是 |2.61 (-3%)
R2R |否 |否 |4.40
R2R |否 |是 |3.63 (-17%)

我们也会检查 FNV 模式,但它看起来在没有图像时效果很好。

抄送@gbalykov @nkaretnikov2

仅供参考,分层编译现在是 .NET Core 的默认设置: https ://github.com/dotnet/coreclr/pull/19525

@alpencolt ,使用 R2R 等 AOT 编译时,启动时间的改进可能会更少。 目前启动时间的改进来自于更少的优化和更快的 jitting,当使用 AOT 编译时,JIT 会更少。 有些方法不是预先生成的,例如一些泛型、IL 存根和其他动态方法。 即使在使用 AOT 编译时,一些泛型也可能在启动期间从分层中受益。

我将继续关闭这个问题,因为我认为@kouvel的承诺已经实现了标题中的要求:D 欢迎人们继续讨论和/或就更具体的主题(例如要求的改进)提出新问题,问题或特定调查。 如果有人认为它过早关闭,当然请告诉我们。

@kouvel很抱歉对已关闭的问题发表评论。 我想知道当使用crossgen等AOT编译时,应用程序是否仍会受益于热点代码路径的第二层编译?

@daxian-dbw 是的,非常如此; 在运行时,Jit 可以进行跨汇编内联(在 dll 之间); 基于运行时常量的分支消除( readonly static ); 等等

@benaadams精心设计的 AOT 编译器不行吗?

我在https://blogs.msdn.microsoft.com/dotnet/2018/08/02/tiered-compilation-preview-in-net-core-2-1/找到了一些关于此的信息:

预编译的映像具有禁止某些类型的优化的版本控制和 CPU 指令约束。 对于这些图像中经常调用的任何方法,分层编译请求 JIT 在后台线程上创建优化代码,以替换预编译版本。

是的,这是“不是精心设计的 AOT”的一个例子。 😛

预编译的映像具有禁止某些类型的优化的版本控制和 CPU 指令约束。

示例之一是使用硬件固有的方法。 AOT 编译器(crossgen)只是假设 SSE2 作为 x86/x64 上的 codegen 目标,因此所有使用硬件内部的方法都将被 crossgen 拒绝,并由知道底层硬件信息的 JIT 编译。

而一个设计良好的 AOT 编译器不能吗?

AOT 编译器需要链接时优化(用于跨程序集内联)和配置文件引导优化(用于运行时常量)。 同时,AOT 编译器在 SIMD 代码的构建时需要“底线”硬件信息(如 gcc/clang 中的-mavx2 )。

示例之一是使用硬件固有的方法。 AOT 编译器(crossgen)只是假设 SSE2 作为 x86/x64 上的 codegen 目标,因此所有使用硬件内部的方法都将被 crossgen 拒绝,并由知道底层硬件信息的 JIT 编译。

等等,什么? 我不太关注这里。 为什么 AOT 编译器会拒绝内在函数?

而一个设计良好的 AOT 编译器不能吗?

AOT 编译器需要链接时优化(用于跨程序集内联)和配置文件引导优化(用于运行时常量)。 同时,AOT 编译器在 SIMD 代码的构建时需要“底线”硬件信息(如 gcc/clang 中的 -mavx2)。

是的,正如我所说,“一个设计良好的 AOT 编译器”。 😁

@masonwheeler不同的场景; crossgen 是与 Jit 一起使用的 AoT,允许对 dll 进行服务/修补,而无需完全重新编译和重新分发应用程序。 它提供比 Tier0 更好的代码生成,比 Tier1 更快的启动; 但不是平台中立的。

Tier0、crossgen 和 Tier1 在coreclr中作为一个内聚模型一起工作

要跨汇编内联(非Jit),它需要编译静态链接的单个文件可执行文件,并且需要完全重新编译和重新分发应用程序以修补它使用的任何库以及针对特定平台(什么版本的使用 SSE、Avx 等;最低通用版本或生产版本?)。

corert将 AoT 这种风格的应用程序。

然而; 做某些类型的分支消除,Jit 可以做的将需要为替代路径生成大量额外的 asm; 以及正确树的运行时修补

例如使用类似方法的任何代码(Tier1 Jit 将删除所有if s)

readonly static _numProcs = Environment.ProcessorCount;

public void DoThing()
{
    if (_numProcs == 1) 
    {
       // Single proc path
    }
    else if (_numProcs == 2) 
    {
       // Two proc path
    }
    else
    {
       // Multi proc path
    }
}

@benaadams

要跨汇编内联(非Jit),它需要编译静态链接的单个文件可执行文件,并且需要完全重新编译和重新分发应用程序以修补它使用的任何库以及针对特定平台(什么版本的使用 SSE、Avx 等;最低通用版本或生产版本?)。

它不应该要求完全重新分发应用程序。 看看 Android 的 ART 编译系统:您将应用程序分发为托管代码(在他们的情况下是 Java,但适用相同的原则),并且存在于本地系统上的编译器,AOT 将托管代码编译成超级优化的本机可执行文件。

如果您更改一些小库,所有托管代码仍然存在,您不必重新分发所有内容,只需带有补丁的内容,然后可以重新运行 AOT 以生成新的可执行文件。 (显然,由于 Android 的 APK 应用程序分发模型,这就是 Android 类比失效的地方,但这不适用于桌面/服务器开发。)

和存在于本地系统上的编译器,AOT 编译托管代码......

那是完整框架使用的以前的 NGen 模型; 虽然不认为它创建了一个将框架代码内联到应用程序代码中的单个程序集? 在 .NET Core 2.1 上运行的 Bing.com 突出显示了两种方法之间的区别! 博文

ReadyToRun 图像

托管应用程序的启动性能通常很差,因为方法首先必须被 JIT 编译为机器代码。 .NET Framework 具有预编译技术 NGEN。 但是,NGEN 要求在执行代码的机器上进行预编译步骤。 对于 Bing 来说,这意味着在数千台机器上进行 NGENing。 随着应用程序在 Web 服务机器上进行预编译,再加上积极的部署周期,将导致服务容量显着降低。 此外,运行 NGEN 需要管理权限,这在数据中心设置中通常不可用或经过严格审查。 在 .NET Core 上,crossgen 工具允许将代码作为预部署步骤进行预编译,例如在构建实验室中,并且部署到生产环境的映像已准备好运行!

@masonwheeler AOT 由于 .Net 进程的动态特性,在整个 .Net 中面临逆风。 例如,.Net 中的方法体可以随时通过探查器进行修改,可以通过反射加载或创建类,并且运行时可以根据需要为互操作创建新代码——因此过程间分析信息最多可以反映正在运行的进程的瞬态。 .Net 中的任何过程间分析或优化(包括内联)必须在运行时撤消。

当在 AOT 时间和运行时之间可能发生变化的事情集很小并且此类变化的影响是本地化的时,AOT 效果最佳,因此可用于 AOT 优化的广泛范围在很大程度上反映了必须始终正确的事情(或者可能有一个小的替代品的数量)。

如果您可以构建机制来应对或限制 .Net 进程的动态特性,那么纯 AOT 可以做得很好——例如 .Net Native 考虑反射和互操作的影响,并且禁止程序集加载、反射发射和(我想)配置文件附加。 但这并不简单。

正在进行一些工作以允许我们将 crossgen 的范围扩展到多个程序集,以便我们可以将所有核心框架(或所有 asp.net 程序集)作为捆绑包进行 AOT 编译。 但这只是可行的,因为当事情发生变化时,我们将 jit 作为重做代码生成的后备。

@AndyAyersMS我从不相信.NET AOT 解决方案应该是“纯AOT”解决方案,这正是您在此处描述的原因。 让 JIT 根据需要创建新代码非常重要。 但是需要它的情况非常少,因此我认为 Anders Hejlsberg 的类型系统规则可以在这里有利地应用:

尽可能静态,必要时动态。

来自 System.Linq.Expressions
公共 TDelegate 编译(bool preferInterpretation);

如果 preferInterpretation 为真,分层编译是否继续工作?

此页面是否有帮助?
0 / 5 - 0 等级