cpu-demo — perf stat 基础指标分析指南
cpu-demo/main.cpp是仓库的核心靶子程序,三种场景覆盖"用户态计算 / 内核态 syscall / 空闲"三种典型性能特征。本文从perf stat角度逐模式解读 8 项默认输出。
准备工作
bash
cd demos/cpu-demo
make # -O0 -g,保留符号和代码原貌
make release # -O2,观察编译器对性能计数的影响模式1: 用户态忙循环 ./cpu_demo 1
bash
# 在另一个终端:
perf stat -p $(pgrep cpu_demo) -- sleep 10预期输出及逐行解读
bash
Performance counter stats for process id '12345':
9,999.87 msec task-clock # 0.999 CPUs utilizedtask-clock: 进程实际占用 CPU 的累计毫秒。10 秒墙钟 = 9999 ms → 单核跑满CPUs utilized = 0.999→ 1 个核的 99.9% 都在跑这个进程
bash
890,123,456 cycles # 0.089 GHz
12,345,678,901 instructions # 3.82 insn per cycle- IPC = 3.82:每周期 3.82 条指令。现代 CPU 宽度 4~6,但实测 IPC 很少达到峰值。这里 -O2 下简单算术被 LLVM/GCC 向量化
- 频率 0.089 GHz?不是——这里
cycles被task-clock算的 GHz 偏低,因为没有乘核心数。看perf stat -e cycles,ref-cycles更准
bash
1,234,567,890 branches # 123.456 M/sec
1,234,567 branch-misses # 0.10% of all branches- 分支预测失败率 0.1% → 极低。
for循环的分支高度规律,预测器完美捕获
bash
0 context-switches # 0.000 /sec
0 cpu-migrations # 0.000 /sec
0 page-faults # 0.000 /sec- 0 context-switches:纯计算进程从不主动放弃 CPU(
while(true) sum+=i不含 sleep) - 0 page-faults:热数据全在寄存器/L1,不触发缺页
一句话诊断
模式 1 是"理想计算负载"的对照组:IPC 高、0 切换、0 缺页、几乎 0 分支预测失败——这是高性能程序的理想态。
模式2: syscall 风暴 ./cpu_demo 2
bash
perf stat -p $(pgrep cpu_demo) -- sleep 10预期输出及逐行解读
bash
9,998.23 msec task-clock # 0.998 CPUs utilized
4,567,890,123 cycles # 0.457 GHz- GHz 偏低:因为大量时间在内核态(
open/write/close),而perf stat默认只统计:u(用户态)事件 - 频率看起来 ~0.5 GHz 但实际全核跑 4 GHz → 这是经典的"
:u陷阱"
bash
1,234,567,890 instructions # 0.27 insn per cycle- IPC 只有 0.27:syscall-heavy 程序的特点是用户态指令很少(调用系统调用本身只有几十条指令),但内核态做了大量工作
- 这个 IPC 是行骗的——只统计用户态指令,漏掉了内核态
bash
12,345,678 context-switches # 1.235 K/sec- context-switches 爆高:每次
open/write/close都可能触发阻塞和重调度 - 1.2 万次/秒 → 虽然不是极端值,但在纯计算模式是 0
关键发现
| 模式 1 (计算) | 模式 2 (syscall) | |
|---|---|---|
| 用户态 IPC | 3.82 | 0.27 |
| 内在性能 | 好 | 不是差,是 :u 统计范围不够 |
| context-switches | 0 | 12345 |
核心教训:syscall-heavy 程序的 perf stat 默认输出不能信。必须加 :u :k 后缀分别统计。
修复::u + :k 完整视角
bash
perf stat -e cycles:u,cycles:k,instructions:u,instructions:k \
-p $(pgrep cpu_demo) -- sleep 5bash
cycles:u = 1,234,567,890 # 用户态
cycles:k = 11,234,000,000 # 内核态 → 这才是大头
instructions:u = 1,234,567,890
instructions:k = 45,678,901,234 # 内核态指令是用户的 37 倍真实 IPC (instructions:u + instructions:k) / (cycles:u + cycles:k) = 3.76 → 根本不差!
详见 perf-case-studies 案例三/四/五/六对
:u陷阱的深度剖析。
模式3: 空闲 ./cpu_demo 3
bash
perf stat -p $(pgrep cpu_demo) -- sleep 10预期输出
bash
87.45 msec task-clock # 0.009 CPUs utilized
48,123,456 cycles
12,345,678 instructions # 0.26 insn per cycle
12 context-switchestask-clock只有 87 ms → 10 秒墙钟中只跑了 87 ms ≈ 0.9% CPU 利用率- 大部分时间在
sleep(100ms)→ TASK_INTERRUPTIBLE 状态 - IPC 偏低是因为
sleep前后的系统调用开销占了 cycles 的大头
常用组合速查
| 我想看... | 命令 |
|---|---|
| 8 项默认指标 | perf stat -p PID -- sleep 5 |
| 对比不同模式 | perf stat -p PID -- sleep 5(运行模式 1→记录,模式 2→对比) |
| 用户态 vs 内核态 | perf stat -e cycles:u,cycles:k -p PID -- sleep 5 |
| 缓存行为 | perf stat -e cache-references,cache-misses -p PID -- sleep 5 |
| 上下文切换 | perf stat -e context-switches,cpu-migrations -p PID -- sleep 5 |
一句话:
perf stat的 8 项默认输出覆盖了"CPU 在不在忙、忙得有没有效率、有没有切换/迁移/缺页"三大基本问题——看懂这 8 行就完成了性能分析的"体检"。
总结
perf stat 八项默认输出完成“CPU 在不在忙、忙得有没有效率、有无切换/迁移/缺页”的体检:cpu-demo 三场景中,纯计算态是高 IPC、零切换缺页的理想对照,syscall 风暴默认只统计用户态(:u 陷阱)会把 IPC 伪造成 0.27,必须用 cycles/instructions 的 :u+:k 双视角才能看到真实 IPC 并不差。