Bing 性能是如何跟随 .NET 一起迭代的?

Songjie Cai

本文翻译于Ben Watson的这篇英文文章:Migration of Bing’s Workflow Engine to .NET 5 – .NET Blog (microsoft.com)

大约两年前,我发表了一篇文章,详细的介绍了Bing的中央工作流引擎(XAP)从.NET Framework升级到.net 5的过程。你可以通过这篇文章来了解XAP的工作原理,以及它在Bing全局中的位置。

从那时起,XAP一直是微软许多搜索和工作流相关技术的关键组件,并在新的集成中发挥了核心作用,比如新的AI驱动的Bing

人们对功能和性能的期望越来越高,这意味着我们对.NET作为基础设施的关键部分的依赖性越来越深。

在过去的两年里,我们是.net 6和.net 7的早期采用者,现在我们正将目光转向.net 8。我们发现每个版本都比上一个版本更容易升级。作为一个核心平台团队,我们对每个版本的 .NET 带来的性能提升和新功能都充满了强烈的动力。通过积极地测试和升级到最新版本,并向.net团队提供反馈,我们也可以影响到他们的计划。这样每个人都会受益。

本文将重点介绍我们所做的一些主要更新和挑战,以及我们在积极跟进最新 .NET 版本时最终取得的胜利。

Hybrid No-More

正如上一篇文章中提到的,当我们第一次升级到.net 5时,我们是在一个混合模型中进行的,其中我们仍然是基于.net Framework 4.x构建的,但是加载程序集并在.net 5下运行。这使我们能够引导我们自己和我们的内部合作伙伴,在保持构建简单的同时保持一些关键的向后兼容性,但仍然利用新的运行时。

在我们升级到。net 6之前,我们迁移到了一个多目标系统,直接针对 .NET Framework 和 .NET 5 进行构建。通过一些条件编译,这使我们可以开始采用新的 API 来获得一些性能优势。

到目前为止,我们已经完全弃用了. Net Framework,并且把精力完全集中在新的运行时上。

.NET 6

随着完成从.net Framework升级的巨大努力,我们预计迁移到.net 6会更容易,事实也的确如此。然而,仍然有一些小挑战和一些意想之外的大的收获,我会详细说明的。

在测试期间,我们注意到一些后端HTTP调用的问题。事实证明,SocketHttpHandler的变化使实现更符合正确的规范,但是没有灵活地处理产生错误负载的错误服务器。. net团队修改了代码,使它更加的兼容。

我们遇到的另一个有趣的运行时问题是一个罕见的旋转计数bug(它很微妙,以至于在生产环境中几个月都没有被注意到,直到它突然变得更糟,原因不明)。它表现为在单个数据中心(可能是因为特定的硬件和流量配置)中,偶尔出现非常高的延迟峰值和较低的整体可用性(因为请求在UX层超时)。实际上,在我们提出这个问题时,. net团队已经修复了它。在应用修复之后,我们看到可用性有了明显且直接的提升:

A graph showing availability percentage, at first wildly fluctuating, then stabilizing at around 100%.

这两个问题都解决后,发布基本上就很简单了,我们只需要最少的代码更改。 最终,整体性能全面提升了约 5%。 然而,有一个方面的改进比这要显著得多:启动时间。

当我们的进程启动时,它会加载几千个程序集(独立开发的插件DLL)。所有这些代码都需要jitted,最好是在真正的用户查询到达之前。多年来,我们已经迭代了许多技术来实现这一点,但我们目前的方法是通过分析JIT事件日志来查看哪些方法最需要它,并在后续的启动中主动地jitting它们。我们尽可能快地在所有处理器内核上执行此操作。

.NET 6的JIT效率因此得到了极大的提升,对我们的启动时间产生了巨大的影响:

A graph showing an enormous drop in startup time from 500 seconds to 300 seconds, coinciding with .NET 6 release.

在一些机器SKU上,启动时间提高了近40%! 令人印象深刻,以至于我们花了很多时间调查是否有什么东西坏了,实际上我们并没有完成所有需要做的工作。但最终,结果是真实且印象深刻的。

.NET 7

疲惫但不能休息!当.net 6发布后,我们就立即集中精力升级到了.net 7。

.NET 7有两个主要的变化需要我们特别注意:

  1. 线程池的操作方式
  2. 一个新的基于区域的GC

详细的测试表明,新的线程池设计为我们带来了更好的性能,所以这里不用担心。

事实上,新的垃圾收集器设计在几个月的时间里由 .NET 开发人员在我们的一些测试机器上进行了具体且广泛的测试,以确保它没有引入任何回归。在一个对运行时如何工作的假设进行了高度优化的系统中,当运行时的一个基本部分极大地改变了其实现时,它总是一个问题。

幸运的是,在测试中,我们发现进程花在GC上的时间平均提高了24%(一开始并不是很多)。在实际生产中,这个比例甚至更高,接近30%。

A graph showing a decline in time spent in GC, coinciding with .NET 7 release.

解决了这两个问题后,我们通常会看到大约10-17%的效率提升,这取决于数据中心和工作负载类型。

很大一部分性能提升来自运行时CPU使用率的提升,也就是在Bing中查询时CPU使用率的降低:

A graph showing a decline in core time per workflow, coinciding with .NET 7 release.

(注意,上图中的值并不代表查询延迟,它是所有并行路径上的CPU使用率的总和。)

在GC改进、新的线程池和对处理器更有效地使用之间,我们实现了P95延迟3-7%的改进,在更高的百分比上改进更多。这种效率的提升让我们能够给用户更快的服务体验,或者在某些情况下,决定减少资源使用并以不同的方式实现运行时收益。“效率”对于科技公司来说就像2023年的年度词汇,而.net 7在我们的努力中发挥了重要作用。

.NET 8!

. Net 8已经发布了预览版。在接下来的几周内,我们将开始在这个新的运行时下测试我们的工作流引擎。到目前为止,年复一年地升级到最新版本已经被证明是显著提高性能的最经济有效的方法。我们从来没有指望“免费”获得所有这些性能提升,平价一直是我们的要求,很难想象这种两位数的效率提升会永远持续下去(尽管目前还没有停止的迹象!)。我们在这里,我们很高兴能站在.NET进步的风口浪尖上!

0 comments

Leave a comment

Feedback usabilla icon