本文整理自 2024 年 4 月 QCon 全球软件开发大会(北京站) 性能优化专题的同名主题分享。
当前数据中心的服务器中部署着各类 CPU(Intel/AMD/Ampere 等),这些平台的差异,使得运行在上面的程序无法保证始终运行在最佳状态,成为了提升业务效能的一大阻碍。
CPU 性能调优,这不仅要求工程师对各个平台有着深入的理解,同时需要掌握各类性能分析工具和方法,并依据得到的观测数据,综合诊断出真正的瓶颈原因,并据此展开优化操作,最终提升业务表现。
这种传统的 CPU 性能调优方式,对工程师的能力和经验提出了相当高的要求,难以在更大规模范围地普及,使得很多客户对表现不理想的程序束手无策。
百度智能云推出的一键调优套件 Btune,实现了整个 CPU 性能调优过程的一键自动化,使得用户能够快速完成瓶颈定位,实现性能优化。
今天我的演讲内容分三个部分:多元 CPU 性能调优的技术挑战;Btune 一键调优产品设计方案;百度智能云的调优实践。
1 多元 CPU 性能调优的技术挑战
随着 AMD/ARM 的 CPU 在数据中心处理器兴起,服务器领域 CPU 近几年呈现多元化发展,Intel/AMD/ARM 并存的趋势。
首先,Intel 仍然是数据中心 CPU 的主流,从 12 年开始的 E5 系列到目前最新的 GNR。
从 17 年开始,AMD 的 Naples 和 Rome 开始进入数据中心领域。到了 21 年,Milan 处理器以其多核优势开始在数据中心大规模上量。
从 21 年开始,Ampere 的数据中心处理器,因为它对比同期其他架构处理器的性价比优势,在数据中心开始占有一席之地。
百度智能云也是 CPU 多元化趋势里面的先行者,以及大规模实践者。
从 2017 年至今先后引入 4 代 AMD 处理器。从 2018 年开始大规模试点 AMD 落地数据库等存储场景开始,之后的每一代 AMD 处理器都有相应的计算产品推出。
从 2022 年至今,先后引入 2 代 ARM 处理器。自 22 年开始,百度内部主流通用计算场景(包括搜索、推荐、商业、大数据等)都已经规模使用了 ARM 处理器。23 年,百度智能云在国内首次发布了 ARM 虚拟硬件产品 AVH。今年,我们在业内首次将 ARM 服务器大规模应用于智驾仿真场景,并基于 ARM 服务器对外推出了车云同构的仿真方案。
那么在 CPU 多元化的当下, 如何进行跨平台性能调优呢?接下来我们从不同层面来看一下。
首先看下 Core 层面。
我们知道,影响 CPU 性能的不只是 CPU 的频率和核心数,还有指令和微架构。
我们以 Intel/AMD/Ampere 三家 CPU 对比可以看到:
SIMD 指令方面:Intel 从 21 年的 Ice Lake 开始就已经支持 AVX512 指令了,截止目前 AMD 一直是 AVX256,直到未来的 Zen4 架构才开始支持 AVX512,Ampere 目前的 Altra 包括最新一代 OneX,都是 128 位宽的 Neon 指令。在一些大位宽并行计算场景,Ampere 相对 Intel 和 AMD 要弱一些。
浮点指令方面:三家对 BF16/FP16 的支持是不一样的,平台是否支持 BF16 或者 FP16,在模型进行 16 bit 量化的时候性能差异很大,在 AI 推理场景 BF16/FP16 的支持是个要特别注意的地方。
频率与 HT 方面:Ampere 是固定频率,不支持 HT,独立的物理核,在云原生场景有优势。Intel 和 AMD 都可以超频,但是有一些差异,Intel 是 all core boost,AMD 的不是 all core boost,这个在一些对频率敏感的场景需要特别关注。
Socket 层面。
SubNUMA 方面,因为 Intel/AMD/Ampere 三者的架构不一样,Intel 是 mesh 架构,AMD 是多 die 多 NUMA 架构,Ampere 是单 die 多 NUMA架构。AMD 和 Ampere 有subNUMA的划分,而且可以看到 AMD 在 subNUMA 内和 subNUMA 间的延时差别要更明显一些。这就导致在一些场景,AMD 做 subNUMA 亲和策略对业务性能会有较大提升。
L3 Cache 方面,Intel/AMD/Ampere 三者的容量和延时都有很大差别,总体来看 Ampere 的 L3 无论容量还是延时性能要差一些。
这里特别提一下 CPM。Ampere 有一个 CPM 的划分,CPM 内的 Core 间通信比 CPM 之间的要小很多,这就导致在一些场景 CPM 亲和策略对 Ampere 机器会有较大的性能提升。
在互联层面。
Intel/AMD/Ampere 采用的是不同的互联协议。
Ampere 采用的开源 CCIX 协议,这个协议的访存延时和 DMA 读带宽,相比另外两种协议要差很多。所以 Ampere 服务器在很多应用场景,是否支持跨路访问对性能影响非常大,需要特别针对跨路访问进行规避。
AMD 的 xGMI 协议,跨路 DMA 读带宽要更大,在一些大内存带宽的场景(比如 EDA 仿真),表现会比较好。
内核层。
这里列举了 4 个例子。
第一个例子是 CPM 亲和。这个问题可以通过内核 patch 进行调度层的 CPM 亲和性优化。当然如果不放到内核层面解决,也可以在用户层面做特殊的绑核逻辑以达到亲和的目的。
第二个例子是 page cache 亲和。内核里面默认是从容器所在 NUMA 来分配 page cache 内存区域。假如容器在 node0 运行,磁盘链接在 node1,就会导致磁盘的 I/O 要从 node1 跨路 DMA 到 node0 的内存区域,这样在跨路延时大的机器上,I/O 性能就会下降很多。
第三个例子是页表管理,在很多业务场景,64K 页面性能要比 4K 页面性能好。但同时大页表也会导致一些业务内存容量占用增加。两者之间的选择需要进行综合考虑。
第四个例子是中断绑定,内核是优先从中断绑定 NUMA 来分配 skb_buffer。假如中断绑定到 node1,网卡链接在 node0,网络数据就需要从 node0 跨路 DMA 到 node1 所在内存区域,这样在跨路延时大的机器上,网络性能就会下降。
Runtime 层面。
不同平台的底层加速指令是不同的。首先 ARM 和 x86 是不同的指令集,其次即使同样是 x86 指令集的 Intel 和 AMD,他们在一些加速指令层面也有差异。
一个应用程序针对平台的指令加速优化,主要可以通过两个环节进行:一个是编译层面的优化;一个是 runtime 层面的优化。
我们把 runtime 划分成了 3 个方面。
第一个是系统基础运行时库。不同版本的运行时库,在有些场景性能差异会非常大,一般来说版本越高性能越好。
第二个是各平台厂商自己提供的专有加速库。为了提升竞争力和壁垒,芯片厂商都会有一些针对自己芯片的加速库,比如 Intel 有自己的 mkl 等库,AMD 有自己的 aocl 等库。这类库有两个问题:库和平台厂商是绑定的,比如同样的压缩操作接口不一致;另外有些库即使可以多平台运行,但是其性能差别会比较大,比如 mkl 库同样的操作在 Intel 平台性能发挥的比较好,到 AMD 平台虽然也能运行,但是因为 mkl 库不会启用 AMD 芯片的很多加速特性,这就导致其性能就会差很多。在跨平台之间进行迁移时候,会遇到相应的性能问题。
第三个是一些高级语言的运行时,如 JDK/Ruby/Python 等。这类运行时除了平台无关的策略优化外(比如 JDK 的 GC 策略、热点运行时编译策略等),针对平台还可以进行很多指令层面的加速优化。
应用层。
这里举了 4 个例子,都是同样的程序往不同平台迁移过程中,遇到的性能问题。
第一个是 BRPC 的例子,我们将一个 BRPC 服务从 x86 迁移到 Ampere 后,发现其 CPU 利用率比 x86 高近一倍。这里的原因就是由于 Ampere 跨路延时高,导致线程切换慢,这个通过调整 BRPC 的协程切换模式后问题得到了解决。
第二个例子是混部冲突。随着节点算力越来越大,单节点可以部署的任务数越来越多,这个会带来很多层面的性能挑战。比如:sys_use 增高、容器监控组件卡死、内核争抢严重、lsof/ps 慢等性能问题。
第三个例子是 Spinlock 锁。x86 平台和 ARM 平台在底层实现机制上有差别。x86 支持 HT,底层通过 pause 指令实现,只让出 HT 计算单元而不释放 CPU。ARM 物理核需要通过 yield 指令实现,有些程序默认使用的 wfe 指令会带来唤醒延时。延时敏感作业需要注意这个差别。
第四个例子是编译。在编译适配环节,程序需要面向不同平台进行优化。比如编译器层面,不同平台需要不同的编译设置才能充分发挥平台性能。基础库层面,不同平台有不同的优化版本。汇编指令层面,有一些程序会直接调用汇编指令进行并行加速,这时候针对不同平台需要进行指令调用层面的优化。
整体来看,性能优化的挑战是多维度的,从硬件层、系统层、runtime 层到应用层,每一层都需要充分优化。
面对多维度性能挑战,如何充分发挥 CPU 性能,确保 SLA 是一个很有挑战的事情。
2 Btune 一键调优产品设计方案
性能调优的典型步骤包括四个环节:指标检测、瓶颈定位、性能优化、业务 SLA。这 4 个环节看起来简单实现起来并不容易。
首先,通过指标检测得到性能瓶颈就是一个很复杂的过程:往往需要借助 10+ 检测工具,围绕 4+ 维度、100+ 指标,进行多个层面的分析,综合后得瓶颈结论。
其次,即使知道了瓶颈在哪里,并不意味着就能优化它:针对每一个瓶颈点,往往需要结合专家经验并反复测试,才可能找到可行的优化方法。
为了让性能调优能够被更多用户使用,所以百度智能云将这 4 步调优过程进行了自动化,推出了 Btune ,实现了一键调优的效果。
接下来,我们首先围绕以上几个过程展开,进一步分享 CPU 性能调优在技术上的挑战,再回到 Btune 本身来介绍他的产品设计思路。
第一步:指标检测。
这里面的主要问题是:从硬件/内核/runtime/应用数据维度很多,这些指标的相关分析工具众多,使用方法和依赖环节复杂。
这个层面的问题我们看到市场上有一些性能分析展示的产品,已经可以一定程度地解决。所以这里不展开了。
第二步:瓶颈分析
从工具指标到瓶颈结论仍然是一个复杂的过程。经典的瓶颈分析方法,我们都知道。
- USE:从资源使用率、饱和度、错误三个维度进行瓶颈分析。看哪里的资源消耗多,是主要的资源瓶颈。
- TSA:通过时间片分析,看哪个环节时间消耗最长。
- TMA:从 CPU 资源角度,去分析程序资源 bound 在哪里。
以上三种方法指向一个核心问题,一个程序在某个方面资源消耗多、时间消耗长、资源 bound,是合理的还是不合理的?其实,有时候是合理的,有时候是不合理的,这个跟程序行为有关。我们要区分它、判断它,才能进一步去看能不能优化它。
这里举一个瓶颈定位的实际生产例子。
这个例子背景是这样的,某个基于 spark 的大数据业务模块,从 x86 平台迁移到 ARM 平台,发现有几个常尾延时高的节点,需要定位性能瓶颈原因。
我们当时的定位过程是这样的:
- 首先看资源指标:CPU/MEM/网络资源使用比较低,I/O 资源无明显区别;
- 然后我们发现网络丢包重传高,同时程序在 DFS 网络数据读取上消耗了过多的时间。为什么会这样呢?
- 进一步观察 DFS 网络数据读写操作,发现 DFS 网络数据读写消耗了大量时间在 skb_buffer 网络数据拷贝;
- 进一步看内核 skb_buffer 数据拷贝,发现长尾节点比非长尾节点存在更多跨路内存数据拷贝。为什么会这样呢?
- 进一步看程序内存分配情况和系统设置,发现长尾节点存在跨路数据拷贝的原因是进程内存没有绑定 NUMA,正常节点是绑定 NUMA 的。
经过以上 5 个步骤,我们才得出结论:程序所在容器没有合理绑定 NUMA,导致 skb_buffer 内存跨路拷贝变慢,影响网络性能。
从这个例子可以看到,要定位到瓶颈结论,往往需要多个层面的信息,综合判断,这是一个比较复杂的过程。
第三步:性能优化。
即使知道了瓶颈点,如何针对瓶颈进行优化,很多情况还是无从下手。
比方说我们分析实际业务的时候,会得出下图这些结论,我们知道了程序的资源瓶颈在哪里、时间消耗在哪里、CPU 资源 bound 在哪里。但怎么优化他们,仍然是个问题。
这里再举一个性能优化的例子。
这个例子的背景是这样的,某个广告检索的 ranker 模块,运行在 x86 平台,希望进一步优化其性能。
我们先后找到 3 个可能的优化点,并进行了优化和测试。
第一个,优化热点函数:
找到程序热点最高的函数 general_top 尝试优化。通过函数 TMA 分析,发现其属于分支预测 miss Bound。进一步分析函数代码,发现有些分支可以优化,于是进行了函数代码的优化。
在经过优化后,函数 30s 内运行耗时从 10s 减少到 7s,减少了 30%,函数优化效果非常明显。但是在经过实际测试后,程序整体性能没有大的变化,平响只降低 0.5ms。
第二个,优化内存分配:
我们发现这个程序消耗了很多时间在内存分配上。同时从火焰图搜索可以看到内存分配栈 calloc 比 malloc 还高。
calloc 代表的是大块内存,分配性能差,经常会有 page fault。malloc 代表的是小块内存,多为红黑树和 hash 表被动分配。所以我们优化代码替换掉 calloc。优化后发现效果不明显,主要原因还是代码修改点在整体耗时占比不高。
第三个,优化内存分配:
我们看到程序整体的读换页占比很高,于是尝试通过使能大页的方式,减少内存换页的压力。
测试发现,使能大页后,读换页从 4.8% 降低到 1.8%。程序延时平均降低 3ms,97 分位 10-12ms,qps 增加 28.5%。程序整体性能优化效果明显。
从这个例子可以看到,要针对某个瓶颈进行优化,往往需要结合专家经验并反复测试,才可能找到可行的优化方法。
这里的主要问题有两个。首先,很多瓶颈点不一定能找到有效的优化方式,需要反复测试验证。其次,有时候我们发现了一个瓶颈点,虽然针对这个瓶颈点我们能优化它,但是如果这个瓶颈不是主要矛盾,即使针对这个瓶颈进行了优化,但是程序整体的性能并不会有太大改善。
针对上面提到的瓶颈定位和性能优化难题,百度智能云推出了如下的解决方案。
- 瓶颈定位:
我们觉得 USE/TSA/TMA 分析方法能够从不同层面得到性能瓶颈,但还不够直接。这些点有可能是主要矛盾,也可能不是主要矛盾。
百度智能云的方案是通过 USE/TSA/TMA 三方面信息,得到程序资源分布、耗时分布、线程关系。然后基于这些信息,利用自研的瓶颈分析树模块,通过自顶向下的方式,从 CPU、内存、磁盘、网络、并行度 5 个维度对负载瓶颈进行全面性能剖析,得到确定性的性能瓶颈点。
- 性能优化:
每个瓶颈点该如何优化,百度智能云目前的方案是基于团队在性能调优过程中的案例,汇总成瓶颈点和优化建议关联知识库,从而实现瓶颈定位和性能优化的闭环。
这里展开介绍下瓶颈分析树。
瓶颈分析树把瓶颈定位的过程逻辑化为树的深度优先遍历过程。遍历的过程就是将负载的瓶颈定位范围逐步缩小的过程,直到叶子结点。下图右边是一个内存部分瓶颈分析树的简易示例图。
以案例 1 为例,首先定位到程序为内存资源瓶颈,然后发现内存读写慢,进一步发现内存 TLBMiss 偏高,进一步发现机器匿名大页未开启。这样一步步遍历,得到瓶颈结论。
下图的案例 2 和案例 3,也是相同的自顶向下的分析过程。
这里展开介绍下性能优化。
瓶颈分析树的每个叶子结点都对应了一个最终的瓶颈结论,而每个叶子结点都有相应的优化建议与之对应。每个瓶颈点所对应到的优化方式,来自于百度多年调优实践案例,这些案例最终汇聚成为了专家知识库。
下面是上文 3 个案例对应的瓶颈结论和可行优化建议。
基于上述方案,百度智能云推出了一款性能调优套件 Btune,支持多元 CPU 加速,目前已经覆盖百度智能云的全部计算类产品。
在 Btune 中选择机器实例和进程后,点击一键分析,几分钟就可以生成性能分析报告,得到程序的瓶颈结论和优化建议。
在 Btune 提供的瓶颈结论和优化建议报告中包含两部分:分析摘要和分析详情。
- 分析摘要:清晰地展示了业务性能瓶颈点和相应的优化建议,可以满足绝大部分场景的需求。
- 分析详情:提供了更详细的性能分析数据,从系统配置、系统性能、进程线程模型、函数指令热点等多个维度呈现负载的运行特性,满足用户更细粒度性能优化。
除了性能调优套件 Btune 之外,百度智能云即将推出加速套件 BtuneAK。
- Btune:一键性能调优助手,可以快速定位瓶颈并得到优化建议。
- BtuneAK:打通性能优化的最后一环,一键使能,提升业务性能。
Btune 相关套件在百度智能云 BBC 和 BCC 计算实例上都是免费使用的。
3 百度智能云的调优实践
第一个例子,检索子系统需要进一步优化延时。通过 Btune 分析得到分析摘要。瓶颈结论为:内存子系统异常,关键瓶颈在 TLB,监测到系统没有开启大页。优化建议为:系统层,系统开启透明大页。使能优化建议后,平均延迟优化 3.9%~4.6%,97 分位延迟优化 4.6%~4.7%。
第二个例子,某排序服务模块基于 BRPC 构建,CPU 利用率高。通过 Btune 分析得到分析摘要。CPU 子系统异常,关键瓶颈在 usr 态,steal_task 操作瓶颈,监测到 Bthread 模式为 steal_task。优化建议为:应用层,优化 Bthread 模式,用 GlobalBalancer 替换 steal_task。使能优化建议后,CPU 平均利用率降低 25.8%。
第三个例子,某数据存储服务,大量的读写请求超过 28ms,突破 SLA 要求。通过 Btune 分析,得到分析摘要。瓶颈结论:进程和读写磁盘分属不同 node 性能差,导致磁盘I/O 瓶颈。优化建议为:硬件层,磁盘亲和(进程所在 node,使用本 node 磁盘)。使能优化建议后:平均请求延时降低 17%,99 分位请求延时降低 11.7%。
以上就是我今天和大家分享的全部内容了。