Redis CPU 性能分析
CPU性能分析和跟踪的性能工程指南
填写性能检查表
Redis 的开发非常注重性能。我们在每个版本中都尽力确保您能体验到非常稳定和快速的产品。
然而,如果您发现有机会提高Redis的效率或正在进行性能回归调查,您将需要一种简洁且有系统的方法来监控和分析Redis的性能。
为此,您可以依赖不同的方法(根据我们打算进行的问题/分析类别,有些方法比其他方法更适合)。Brendan Greg 在以下链接中列举了一系列精选的方法及其步骤。
我们推荐使用利用率、饱和度和错误(USE)方法来回答什么是你的瓶颈问题。查看以下系统资源、指标和工具之间的映射,以进行实际的深入探讨: USE方法。
确保CPU成为你的瓶颈
本指南假设您已按照上述方法之一执行了系统健康的全面检查,并确定瓶颈在于CPU。如果您已确定大部分时间都阻塞在I/O、锁、定时器、分页/交换等方面,本指南不适合您。
构建先决条件
为了进行正确的CPU分析,Redis(以及任何动态加载的库,如Redis模块)需要堆栈跟踪对追踪器可用,您可能需要先解决这个问题。
默认情况下,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文档以了解高级过滤、排序和聚合功能。
使用火焰图可视化记录的配置文件信息
Flame graphs 允许快速且准确地可视化频繁的代码路径。它们可以使用 Brendan Greg 在 github 上的开源程序生成,这些程序从折叠的堆栈文件创建交互式 SVG。
具体来说,对于性能分析,我们需要将生成的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资源的开销。
除此之外,如果我们主要目标是堆栈跟踪分析,我们还移除了perf.data和中间步骤,仅依赖bcc/BPF的性能分析工具。你可以使用bcc的性能分析工具直接输出折叠格式,用于火焰图生成:
$ /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次,等等。
使用性能监控计数器(PMCs)进行硬件事件计数
许多现代处理器包含一个性能监控单元(PMU),它暴露了性能监控计数器(PMCs)。PMCs对于理解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
重要的是要知道PMCs有两种非常不同的使用方式(计数和采样),为了本次分析,我们仅关注PMCs的计数。Brendan Greg在以下链接中对此进行了清晰的解释。