.NET8 上的 Bing :动态PGO的影响

自从我上次更新大家有关.NET在Bing技术栈中的状态以来已经过去了一年多,尤其是位于核心位置的高性能工作流执行引擎。在这段时间里,这个引擎的应用范围只增不减,特别是随着Microsoft Copilot的发布。虽然我们的工作流引擎起源于Bing,但现在可以说它支撑了许多Microsoft应用程序中搜索和数据栈的相当大一部分。

我们从.NET 8的早期预览版开始进行测试。尽管在.NET 8的核心库中有明显的性能递增好处,但促使我们升级的最大因素是对动态PGO(Profile Guided Optimization,配置文件指导的优化)的显著改进。这一功能自.NET 6起就以预览形式存在,在.NET 8中,改进足够显著,以至于默认启用了这一功能。

之前的帖子:

迁移Bing工作流引擎到 .NET5

.NET 5 升级到 .NET 7,再次为必应带来性能提升

动态PGO

鉴于我们的规模,有时会有一些功能开箱即用就能在几乎所有应用中表现良好,但我们仍会给予额外的考虑。

在进程启动时,这个服务器会加载数千个合作伙伴组件,这些组件包含了我们执行工作流程所需的插件。这大约有2GB的代码,其中很多都需要即时编译(JIT)。当第一个用户查询请求击中服务器时,它需要能够在几百毫秒内提供答案,同时避免因即时编译而导致的暂停。

人们自然会想到,像NGEN和Ready2Run这样的技术是否会有所帮助。我们尝试过预编译代码,并取得了不同程度的成功。最终,我们发现最佳的性能与启动时间最小化的平衡是让JIT自行处理,结合在以往运行中检测到的特别关键的方法列表进行预编译。我们在启动时并行加载其他数据时做这件事。我们还在接受真实用户流量之前,通过系统推送一些测试查询。

动态PGO通过根据需要重新编译某些代码来提高运行时代码的质量。理论上,这可以帮助我们改善延迟,但我们需要彻底测试它对启动和第一个用户查询的影响。关于动态PGO的工作原理,你可以在其他地方阅读到,但简而言之:

动态PGO会在新即时编译的代码中加入一些轻量级指令,以记录性能特征并建立一个重新编译候选队列。可能的优化包括:

  • 内联
  • 方法去虚拟化
  • 循环优化
  • 尾递归移除
  • 优化内存中的代码布局以优化处理器缓存
  • ……还有更多

在测试中,我们看到了两个主要结果:

  1. 稳定状态下性能显著提升。
  2. 我们最大的工作负载在第一个用户查询时有一个小的延迟峰值,这表明要么有些方法根本没有编译,要么是因为分析的影响太大。这使得总延迟超出了最高限制。对于我们较小的工作负载来说,这不是问题。
    下面的延迟图显示与基线相比,延迟有一个大幅度的峰值:

(请注意,本文档中的图表已删除了特定的内部指标信息,但形状的变化应能让您大致了解相对变化情况。)

在深入调查和来自 .NET 团队的帮助后,我们发现在我们遇到第一个用户查询之前,重新编译(re-jit)队列的大小已经增长到超过300,000个方法!正如我所说,我们的代码量非常大。最终,当进程运行几个小时后,我们通常会有超过两百万个方法。

作为响应,我们实施了一些小的改动:

  1. 增加了额外的预热查询,给更多方法机会进行即时编译(jit)和重新编译(re-jit)。
  2. 在接受用户流量之前稍作暂停,以便让队列有时间排空(我们起初将其静态配置,但有一个事件可以让您监控 JIT 队列大小)。
  3. 一些针对我们超大场景的自定义 JIT 设置:
REM 启用 64 位计数器以稍微减少错误共享
set DOTNET_JitCollect64BitCounts=1

REM 移除启动分层编译的延迟
REM (目标是减少总体上执行带有检测代码的方法所花费的时间)
set DOTNET_TC_CallCountingDelayMs=0

有了这些变化,延迟峰值消失了,现在我们可以享受稳定状态下的性能改进。

性能提升

我们所见到的在多个性能特征上的改进,或许是自从从.NET Framework迁移到.NET 5以来最显著的一次。

我们执行一个查询所消耗的CPU周期数减少了13%。换句话说,我们的效率提高了13%,或者再换一种方式表达:这意味着我们需要购买的机器数量减少了13%,以应对不断增长的需求。下面的图表显示了执行工作流程时总CPU时间的下降。

一个图表显示了查询执行中CPU时间的减少。

受到gen0或gen1垃圾回收影响的查询比例下降了20%(我们几乎从不会有查询受到gen2 GC的影响,因为A. 我们的内存管理策略避免了这一点,B. 我们采取了额外的措施以确保机器在gen2 GC迫在眉睫时不会服务用户——这是极其罕见的)。

这个图表显示了GC对查询影响的相对差异:

图表显示了受垃圾回收影响的查询百分比的下降。许多其它指标也显示出查询执行各个阶段有类似上述图表的改进。一些内部延迟降低了超过25%。查询服务的很大一部分是等待其他后端,因此总体查询服务时间的改进幅度稍微小一些,大约8%——但这仍然非常显著!

总结

总的来说,这次的 .NET 发布对我们来说既稳固又相对容易。我们在延迟上取得了改进,在效率上也有了巨大提升,这将在未来几年为我们节省数百万美元。尽管在我们庞大的代码库和严格的延迟要求下,动态 PGO 需要一些微调,但在运行时性能方面,它确实是一个巨大的胜利。

现在,我需要开始为 .NET 9 做准备了……希望在明年能再次向你们报告!

原文信息

作者:Ben Watson Principal Software Engineer, Bing Platform

链接:https://devblogs.microsoft.com/dotnet/bing-on-dotnet-8-the-impact-of-dynamic-pgo/

.NET性能优化交流群

相信大家在开发中经常会遇到一些性能问题,苦于没有有效的工具去发现性能瓶颈,或者是发现瓶颈以后不知道该如何优化。之前一直有读者朋友询问有没有技术交流群,但是由于各种原因一直都没创建,现在很高兴的在这里宣布,我创建了一个专门交流.NET性能优化经验的群组,主题包括但不限于:

  • 如何找到.NET性能瓶颈,如使用APM、dotnet tools等工具
  • .NET框架底层原理的实现,如垃圾回收器、JIT等等
  • 如何编写高性能的.NET代码,哪些地方存在性能陷阱

希望能有更多志同道合朋友加入,分享一些工作中遇到的.NET性能问题和宝贵的性能分析优化经验。目前一群已满,现在开放二群。

如果提示已经达到200人,可以加我微信,我拉你进群: ls1075

另外也创建了QQ群,群号: 687779078,欢迎大家加入。

image-20230703203249615

  • 17
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值