文档:CPU 分析

完善性能清单

Valkey 在开发过程中非常注重性能。我们尽最大努力确保每个版本都能为您带来稳定、快速的产品体验。

然而,如果您发现 Valkey 仍有提升效率的空间,或正在进行性能退化调查,您将需要一套简洁系统的方法来监控和分析 Valkey 性能。

为此,您可以依赖不同的方法论(其中一些方法根据我们打算进行的分类问题/分析比其他方法更适用)。Brendan Greg 在以下链接中列举了经过整理的方法论及其步骤。

我们推荐使用利用率、饱和度和错误 (USE) 方法来回答瓶颈所在的问题。请查看系统资源、指标和工具之间的以下映射,以便进行实际的深入分析:USE 方法

确保 CPU 是您的瓶颈

本指南假设您已遵循上述方法之一对系统健康状况进行了全面检查,并确定瓶颈在于 CPU。如果您已确定大部分时间都花在 I/O、锁、计时器、分页/交换等阻塞上,则本指南不适合您

构建先决条件

为了进行正确的 On-CPU 分析,Valkey(以及任何动态加载的库,如 Valkey 模块)要求堆栈跟踪对追踪器可用,这可能需要您先进行修复。

默认情况下,Valkey 使用 -O3 优化标志进行编译(我们打算在性能分析期间保留此设置)。这意味着编译器优化已启用,可显著提高性能。Valkey 默认也使用 -fno-omit-frame-pointer 标志进行编译,确保帧指针在函数调用之间得以保留。这种组合允许精确的堆栈遍历和调用堆栈跟踪,这对于准确的性能分析和调试至关重要。保持帧指针完整有助于 perfgdb 等性能分析工具将 On-CPU 时间正确归因于更深层的调用堆栈帧,从而对性能瓶颈和热点提供更可靠的洞察。这种设置在保持高度优化的可执行文件与确保性能分析和跟踪工具提供准确且可操作的数据之间取得了平衡。

请务必确保

  • 我们仍使用优化来获得生产运行时长的准确表示,这意味着我们将保留:-O3

您可以在 Valkey 主仓库中按如下方式进行操作

$ make SERVER_CFLAGS="-g"

一套用于识别性能退化和/或潜在的 On-CPU 性能改进的工具

本文档专门关注 On-CPU 资源瓶颈分析,这意味着我们有兴趣了解线程在 On-CPU 运行时 CPU 周期花费在哪里,同样重要的是,这些周期是否有效地用于计算,还是停滞等待(未阻塞!)内存 I/O 和缓存未命中等。

为此,我们将依赖工具包(perf、bcc 工具)和特定硬件的 PMCs(性能监控计数器)来继续进行

  • 热点分析(perf 或 bcc 工具):用于分析代码执行情况,确定哪些函数耗时最多,从而成为优化的目标。我们将介绍两种选项来使用 perf 或 bcc/BPF 跟踪工具收集、报告和可视化热点。

  • 调用计数分析:用于计算事件,包括函数调用,使我们能够同时关联多个调用/组件,依赖于 bcc/BPF 跟踪工具。

  • 硬件事件采样:对于理解 CPU 行为至关重要,包括内存 I/O、停滞周期和缓存未命中。

工具先决条件

以下步骤依赖于 Linux perf_events(又名“perf”)、bcc/BPF 跟踪工具以及 Brendan Greg 的 FlameGraph 仓库

我们假设您已预先

  • 在您的系统上安装了 perf 工具。大多数 Linux 发行版可能会将其打包为与内核相关的软件包。有关 perf 工具的更多信息可以在 perf wiki 上找到。
  • 按照安装 bcc/BPF 的说明在您的机器上安装了 bcc 工具包。
  • 克隆了 Brendan Greg 的 FlameGraph 仓库,并使 difffolded.plflamegraph.pl 文件可访问,以生成折叠的堆栈跟踪和火焰图。

使用 perf 或 eBPF 进行热点分析(堆栈跟踪采样)

通过定时采样堆栈跟踪来分析 CPU 使用率是一种快速简便的方法,用于识别性能关键的代码段(热点)。

使用 perf 采样堆栈跟踪

要以每秒 999 次的采样频率,对 valkey-server 的用户级和内核级堆栈进行特定时间长度(例如 60 秒)的性能分析

$ perf record -g --pid $(pgrep valkey-server) -F 999 -- sleep 60

使用 perf report 显示记录的性能分析信息

默认情况下,perf record 会在当前工作目录中生成一个 perf.data 文件。

然后,您可以使用调用图输出(调用链,堆栈回溯)进行报告,最小调用图包含阈值为 0.5%,方法是

$ perf report -g "graph,0.5,caller"

有关高级过滤、排序和聚合功能,请参阅 perf report 文档。

使用火焰图可视化记录的性能分析信息

火焰图可以快速准确地可视化频繁的代码路径。它们可以使用 Brendan Greg 在 GitHub 上的开源程序生成,这些程序从折叠的堆栈文件中创建交互式 SVG。

具体来说,对于 perf,我们需要将生成的 perf.data 转换为捕获的堆栈,并将每个堆栈折叠成单行。然后,您可以使用以下命令渲染 On-CPU 火焰图

$ perf script > valkey.perf.stacks
$ stackcollapse-perf.pl valkey.perf.stacks > valkey.folded.stacks
$ flamegraph.pl valkey.folded.stacks > valkey.svg

默认情况下,perf script 会在当前工作目录中生成一个 perf.data 文件。有关高级用法,请参阅 perf script 文档。

有关更高级的堆栈跟踪可视化(如差异化可视化),请参阅 FlameGraph 使用选项

归档和共享记录的性能分析信息

为了在收集数据以外的机器上分析 perf.data 内容,您需要将 perf.data 文件以及记录数据文件中找到的所有具有构建 ID 的目标文件一起导出。这可以通过 perf-archive.sh 脚本轻松完成

$ perf-archive.sh perf.data

现在请运行

$ tar xvf perf.data.tar.bz2 -C ~/.debug

在需要运行 perf report 的机器上。

使用 bcc/BPF 的 profile 工具采样堆栈跟踪

与 perf 类似,自 Linux 内核 4.9 起,BPF 优化的性能分析现已完全可用,并有望在性能分析期间降低 CPU(因为堆栈跟踪在内核上下文中进行频率计数)和磁盘 I/O 资源的开销。

除此之外,如果堆栈跟踪分析是我们的主要目标,我们还完全依赖 bcc/BPF 的 profile 工具,从而省去了 perf.data 和中间步骤。您可以使用 bcc 的 profile 工具直接输出折叠格式,用于生成火焰图

$ /usr/share/bcc/tools/profile -F 999 -f --pid $(pgrep valkey-server) --duration 60 > valkey.folded.stacks

这样,我们去除了任何预处理,只需一个命令即可渲染 On-CPU 火焰图

$ flamegraph.pl valkey.folded.stacks > valkey.svg

使用火焰图可视化记录的性能分析信息

使用 bcc/BPF 进行调用计数分析

一个函数可能消耗大量的 CPU 周期,这可能是因为其代码运行缓慢,也可能是因为它被频繁调用。要了解函数的调用频率,您可以使用 BCC 的 funccount 工具进行调用计数分析

$ /usr/share/bcc/tools/funccount 'valkey-server:(call*|*Read*|*Write*)' --pid $(pgrep valkey-server) --duration 60
Tracing 64 functions for "valkey-server:(call*|*Read*|*Write*)"... Hit Ctrl-C to end.

FUNC                                    COUNT
call                                      334
handleClientsWithPendingWrites            388
clientInstallWriteHandler                 388
postponeClientRead                        514
handleClientsWithPendingReadsUsingThreads      735
handleClientsWithPendingWritesUsingThreads      735
prepareClientToWrite                     1442
Detaching...

以上输出显示,在跟踪期间,Valkey 的 call() 函数被调用了 334 次,handleClientsWithPendingWrites() 被调用了 388 次,等等。

使用性能监控计数器 (PMCs) 进行硬件事件计数

许多现代处理器都包含一个性能监控单元 (PMU),它暴露了性能监控计数器 (PMCs)。PMCs 对于理解 CPU 行为至关重要,包括内存 I/O、停滞周期和缓存未命中,并提供在其他地方无法获得的底层 CPU 性能统计数据。

PMU 的设计和功能是 CPU 特定的,您应该使用 perf list 来评估您的 CPU 支持的计数器和功能。

为了计算每周期指令数、执行的微操作数、没有微操作分派的周期数、内存上的停滞周期数(包括每种内存类型的停滞),持续 60 秒,特别是针对 valkey-server 进程

$ perf stat -e "cpu-clock,cpu-cycles,instructions,uops_executed.core,uops_executed.stall_cycles,cache-references,cache-misses,cycle_activity.stalls_total,cycle_activity.stalls_mem_any,cycle_activity.stalls_l3_miss,cycle_activity.stalls_l2_miss,cycle_activity.stalls_l1d_miss" --pid $(pgrep valkey-server) -- sleep 60

Performance counter stats for process id '3038':

  60046.411437      cpu-clock (msec)          #    1.001 CPUs utilized          
  168991975443      cpu-cycles                #    2.814 GHz                      (36.40%)
  388248178431      instructions              #    2.30  insn per cycle           (45.50%)
  443134227322      uops_executed.core        # 7379.862 M/sec                    (45.51%)
   30317116399      uops_executed.stall_cycles #  504.895 M/sec                    (45.51%)
     670821512      cache-references          #   11.172 M/sec                    (45.52%)
      23727619      cache-misses              #    3.537 % of all cache refs      (45.43%)
   30278479141      cycle_activity.stalls_total #  504.251 M/sec                    (36.33%)
   19981138777      cycle_activity.stalls_mem_any #  332.762 M/sec                    (36.33%)
     725708324      cycle_activity.stalls_l3_miss #   12.086 M/sec                    (36.33%)
    8487905659      cycle_activity.stalls_l2_miss #  141.356 M/sec                    (36.32%)
   10011909368      cycle_activity.stalls_l1d_miss #  166.736 M/sec                    (36.31%)

  60.002765665 seconds time elapsed

需要注意的是,PMCs 有两种截然不同的使用方式(计数和采样),而为了本次分析,我们只关注 PMCs 计数。Brendan Greg 在以下链接中对此进行了清晰的解释。