Redis CPU 分析
片上 CPU 分析和跟踪的性能工程指南
Redis 开源版 |
---|
完成性能检查清单
Redis 在开发过程中非常注重性能。我们在每个版本中都尽最大努力确保您获得非常稳定和快速的产品体验。
然而,如果您发现 Redis 效率有待提高的空间,或者正在进行性能回退调查,您将需要一种简洁有序的方法来监控和分析 Redis 性能。
为此,您可以依赖不同的方法(有些方法比其他方法更适合,具体取决于我们打算进行的问题/分析类别)。 Brendan Greg 在以下链接中列举了精心挑选的方法及其步骤。
我们建议使用利用率、饱和度错误 (USE) 方法来回答您的瓶颈是什么的问题。查看以下系统资源、指标和工具之间的映射,进行实际深入分析:USE 方法。
确保 CPU 是您的瓶颈
本指南假设您已遵循上述方法之一对系统健康状况进行了全面检查,并确定瓶颈是 CPU。如果您发现大部分时间都花在 I/O、锁、定时器、分页/交换等阻塞上,则本指南不适合您。
构建先决条件
为了进行适当的片上 CPU 分析,Redis(以及任何动态加载的库,例如 Redis Modules)需要向跟踪器提供堆栈跟踪,您可能需要先解决此问题。
默认情况下,Redis 使用 -O2
开关编译(我们打算在分析期间保留此设置)。这意味着编译器优化已启用。许多编译器会省略帧指针作为运行时优化(节省一个寄存器),从而破坏基于帧指针的堆栈遍历。这使得 Redis 可执行文件更快,但同时它也使得 Redis(像任何其他程序一样)更难跟踪,可能错误地将片上 CPU 时间指向调用堆栈中最后一个可用的帧指针,而该调用堆栈实际上可以更深(但无法跟踪)。
重要的是您要确保
- 存在调试信息:编译选项
-g
- 存在帧指针寄存器:
-fno-omit-frame-pointer
- 我们仍然使用优化来获得生产运行时间的准确表示,这意味着我们将保留:
-O2
您可以在 redis 主仓库中按如下方式进行
$ make REDIS_CFLAGS="-g -fno-omit-frame-pointer"
一套用于识别性能回退和/或潜在片上 CPU 性能改进的工具
本文档专门重点介绍片上 CPU 资源瓶颈分析,这意味着我们感兴趣的是了解线程在片上 CPU 运行时消耗 CPU 周期的位置,同样重要的是,这些周期是有效地用于计算还是停滞等待(非阻塞!)内存 I/O 和缓存未命中等。
为此,我们将依赖工具包(perf、bcc 工具)和硬件特定的 PMC(性能监控计数器)来继续进行
-
热点分析(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.pl
和flamegraph.pl
文件可访问,以生成折叠的堆栈跟踪和火焰图。
使用 perf 或 eBPF 进行热点分析(堆栈跟踪采样)
通过按设定的时间间隔对堆栈跟踪进行采样来分析 CPU 使用率是一种快速简便的方法,可以识别性能关键的代码段(热点)。
使用 perf 进行堆栈跟踪采样
要对 redis-server 的用户和内核级堆栈进行特定时间的分析,例如 60 秒,采样频率为每秒 999 次采样
$ perf record -g --pid $(pgrep redis-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 转换为捕获的堆栈,并将每个堆栈折叠成单行。然后您可以使用以下命令渲染片上 CPU 火焰图
$ perf script > redis.perf.stacks
$ stackcollapse-perf.pl redis.perf.stacks > redis.folded.stacks
$ flamegraph.pl redis.folded.stacks > redis.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 redis-server) --duration 60 > redis.folded.stacks
这样,我们就消除了任何预处理,并且可以通过一个命令渲染片上 CPU 火焰图
$ flamegraph.pl redis.folded.stacks > redis.svg
使用火焰图可视化记录的分析信息
使用 bcc/BPF 进行调用计数分析
一个函数可能消耗大量 CPU 周期,这可能是因为其代码运行缓慢,也可能是因为它被频繁调用。要确定函数被调用的速率,您可以使用 BCC 的 funccount
工具进行调用计数分析
$ /usr/share/bcc/tools/funccount 'redis-server:(call*|*Read*|*Write*)' --pid $(pgrep redis-server) --duration 60
Tracing 64 functions for "redis-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...
上述输出显示,在跟踪期间,Redis 的 call() 函数被调用了 334 次,handleClientsWithPendingWrites() 函数被调用了 388 次,等等。
使用性能监控计数器 (PMC) 进行硬件事件计数
许多现代处理器包含性能监控单元 (PMU),该单元暴露性能监控计数器 (PMC)。PMC 对于理解 CPU 行为至关重要,包括内存 I/O、停滞周期和缓存未命中,并提供其他任何地方都无法获得的低级 CPU 性能统计信息。
PMU 的设计和功能是 CPU 特定的,您应该使用 perf list
评估您的 CPU 支持的计数器和功能。
要计算每周期指令数、执行的微操作数、没有分派微操作的周期数、内存停滞周期数(包括每种内存类型的停滞),持续时间为 60 秒,特别是针对 redis 进程
$ 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 redis-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
重要的是要了解 PMC 有两种截然不同的使用方式(计数和采样),而我们为了本次分析的目的,只专注于 PMC 计数。Brendan Greg 在以下链接中对此进行了清晰的解释。