2. 内存带宽瓶颈识别:用 perf stat、sar、vmstat 初步判断

好,咱们进入正题。内存带宽不足这事儿,很多时候不是一眼能看出来的。CPU 利用率不高、业务响应变慢,你第一反应可能是“代码写得烂”或者“磁盘 IO 扛不住了”。但我在项目中遇到过好几次,折腾半天,最后发现是内存带宽把 CPU 给“饿”着了。

所以这一节,我带你用几个最常用的工具,先做个快速筛查。说白了,就是给系统把个脉,看看它是不是“吃不饱饭”。

2.1 为什么先看这些工具?

你想想看,排查性能问题,最忌讳一上来就上大杀器。perf、sar、vmstat 这三个工具,几乎每个 Linux 发行版都自带,或者很容易装上。它们能给你一个宏观的、初步的判断。

我个人习惯是:先跑一轮这些命令,看看有没有“异常信号”。如果有,再深入用 perf stat 去抓具体事件。如果没有,那大概率是别的问题,别在内存带宽上浪费时间。

核心思路: 内存带宽瓶颈的典型表现是——CPU 在大量时间处于“ stalled ”状态,也就是停顿等待数据。工具就是帮我们抓这个“停顿”的。

2.2 用 vmstat 看系统整体“饥饱”

vmstat 是我最常用的第一道手。它报告的是整个系统的统计信息,简单粗暴。

# 每隔 1 秒输出一次,共输出 5 次
vmstat 1 5

输出大概长这样(我简化了关键列):

procs memory swap io system cpu
r b swpd free buff cache si so bi bo in cs us sy id wa st
2 0 0 1623456 1234 567890 0 0 10 20 1500 2000 30 15 55 0 0

看哪里?重点关注 cpu 列下的 wa(iowait) 和 system 列下的 cs(context switch)。

  • wa 不高,但 us 也不高? 嗯,这里要注意。如果 CPU 利用率(us+sy)不高,但业务就是慢,而且 wa 也很低,那 CPU 很可能在“空转等待内存”。vmstat 不会直接告诉你,但这是一个重要的间接信号。
  • cs(上下文切换)异常高? 比如每秒几万甚至几十万次。这通常意味着有大量线程在争抢锁或者资源。但有时候,内存带宽不足导致 CPU 停顿,操作系统会误以为 CPU 空闲,从而调度其他线程上来,反而加剧了切换。我曾经在一个数据库服务器上看到 cs 冲到 20 万,查了半天,最后发现是 NUMA 节点间内存访问延迟导致的带宽问题。

我的小技巧: 如果 vmstat 里的 r(运行队列)很大,但 us 却不高,比如 r=8 但 us=40%,这基本就是 CPU 在等数据。八九不离十是内存或者缓存的问题。

2.3 用 sar 看内存带宽的“影子指标”

sar 是系统活动报告器,它能记录历史数据。我个人习惯用 sar -B 来看分页活动,这跟内存带宽有很强的关联。

# 实时查看,每隔 1 秒一次
sar -B 1 3

输出关键列:

pgpgin/s pgpgout/s fault/s majflt/s pgfree/s pgscank/s pgscand/s pgsteal/s %vmeff
0.00 12.00 15.00 0.00 20.00 0.00 0.00 0.00 0.00

重点看 pgscank/s 和 pgscand/s。这两个是“页面回收”相关的指标。如果它们持续大于 0,尤其是 pgscand/s(直接页面扫描)很高,说明内存压力很大,系统在拼命回收内存页。这会导致严重的性能抖动,因为回收过程本身就要占用内存带宽。

为什么会这样?因为内存带宽不足时,数据在内存和 CPU 之间“跑不动”,导致缓存命中率下降,进而触发更多的缺页中断和页面回收。这是一个恶性循环。

避坑指南: 我曾经在排查一个 Java 应用时,看到 pgscand 很高,以为是内存泄漏。结果用 perf 一抓,发现是 JVM 的 GC 线程在疯狂扫描内存,而根本原因是 NUMA 内存分配策略不对,导致跨节点访问,带宽被严重拖累。所以,sar 的指标是“果”,不是“因”。

2.4 用 perf stat 抓“真凶”

好了,vmstat 和 sar 给了我们“嫌疑”,现在该 perf 上场了。perf stat 可以直接读取 CPU 的性能计数器,这是最直接的证据。

# 统计整个系统的 CPU 周期和 stalled 周期
perf stat -e cycles,stalled-cycles-frontend,stalled-cycles-backend -a sleep 5

输出示例:

 Performance counter stats for 'system wide':

    12,345,678,901      cycles                    #    2.47 GHz
     4,567,890,123      stalled-cycles-frontend   #   37.0% frontend cycles idle
     6,789,012,345      stalled-cycles-backend    #   55.0% backend cycles idle

       5.001234567 seconds time elapsed

这里有两个关键指标:

  • stalled-cycles-frontend:前端停顿。指 CPU 在等待指令获取(比如指令缓存 miss)。这通常跟代码体积大、分支预测失败有关,但也可能跟内存带宽有关(如果指令缓存被冲掉)。
  • stalled-cycles-backend:后端停顿。指 CPU 在等待数据(比如数据缓存 miss、内存访问)。这个指标跟内存带宽直接相关。 如果它超过 40%,甚至 50% 以上,基本可以断定内存带宽是瓶颈。

我个人习惯是,如果 stalled-cycles-backend 占比超过 30%,我就会进一步用 perf stat -e LLC-load-misses,LLC-store-misses 去看 LLC(最后一级缓存)的 miss 情况。

perf stat -e LLC-load-misses,LLC-store-misses -a sleep 5

如果 LLC-load-misses 很高,说明数据在 LLC 里找不到,必须去内存里拿。这时候内存带宽的压力就非常大了。

一个简单的判断标准:

  • stalled-cycles-backend < 20%:内存带宽大概率不是问题。
  • stalled-cycles-backend 20% ~ 40%:有压力,需要进一步分析。
  • stalled-cycles-backend > 40%:内存带宽很可能是主要瓶颈。

2.5 一张图理清思路

下面这张图,是我自己总结的排查流程。你照着走一遍,基本不会漏掉关键点。

内存带宽瓶颈初步排查流程 业务变慢 / CPU 利用率不高 步骤1: vmstat 1 5 看 r 队列高但 us 低?cs 异常高? 有异常? 是 步骤2: sar -B 1 3 看 pgscand 是否持续 > 0 否 步骤3: perf stat -e stalled-cycles-backend 看 backend stalled 是否 > 30% 结论:内存带宽很可能是瓶颈

2.6 小结一下

这一节的内容,说白了就是三板斧:

  1. vmstat 看宏观异常(r 队列 vs us 的背离、高 cs)。
  2. sar -B 看内存回收压力(pgscand)。
  3. perf stat 看 CPU 停顿(stalled-cycles-backend)。

这三步走完,你心里基本就有数了。如果三个工具都指向内存带宽,那别犹豫,下一节我们就要深入用 perf 去抓具体是哪个程序、哪段代码在“抢带宽”了。

嗯,记住,工具是死的,思路是活的。别光看数字,要结合你的业务场景去理解这些数字背后的含义。

最后提醒一句: 跑 perf stat 的时候,记得加上 -a 参数统计整个系统,或者用 -p 指定进程。别只盯着一个 CPU 看,内存带宽是全局资源。

可可精品资料库