.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 设置:
sh 复制代码
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

链接:devblogs.microsoft.com/dotnet/bing...

相关推荐
chxii27 分钟前
Spring Boot 响应给客户端的常见返回类型
java·spring boot·后端
韩立学长30 分钟前
【开题答辩实录分享】以《植物爱好者交流平台的设计与实现》为例进行答辩实录分享
spring boot·后端·mysql
Wzx19801232 分钟前
go基础语法练习
开发语言·后端·golang
sp421 小时前
漫谈 Java 轻量级的模板技术:从字符串替换到复杂模板
java·后端
2301_795167201 小时前
玩转Rust高级应用. ToOwned trait 提供的是一种更“泛化”的Clone 的功能,Clone一般是从&T类型变量创造一个新的T类型变量
开发语言·后端·rust
草莓熊Lotso1 小时前
C++ 方向 Web 自动化测试实战:以博客系统为例,从用例到报告全流程解析
前端·网络·c++·人工智能·后端·python·功能测试
一 乐2 小时前
旅游|内蒙古景点旅游|基于Springboot+Vue的内蒙古景点旅游管理系统设计与实现(源码+数据库+文档)
开发语言·前端·数据库·vue.js·spring boot·后端·旅游
JaguarJack2 小时前
15 个 Eloquent 高级技巧,瞬间提升你的 Laravel 应用性能
后端·php·laravel
YDS8292 小时前
苍穹外卖 —— Spring Cache和购物车功能开发
java·spring boot·后端·spring·mybatis
苍老流年2 小时前
1. SpringBoot初始化器ApplicationContextInitializer使用与源码分析
java·spring boot·后端