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.plflamegraph.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在以下链接中对此进行了清晰的解释。

RATE THIS PAGE
Back to top ↑