﻿# cpu-demo — perf record + 火焰图分析指南

> 用 `perf record` 采样 `cpu-demo` 三模式的调用栈，生成火焰图，对比 O0 vs O2 下完全不同热点分布。

## 准备工作

```bash
cd demos/cpu-demo
make          # -O0 -g (默认)
make release  # -O2 (可选对比)
```

## 模式1: 用户态忙循环 — 采样 + 火焰图

### 录制

```bash
# 终端 A: 启动程序
./cpu_demo
# 输入 1 (用户态忙循环)
# 终端 B: 录制 30 秒
sudo perf record -F 99 -g -p $(pgrep cpu_demo) -- sleep 30
```

### 看报告

```bash
perf report --stdio
```

**预期输出**：

```bash
# Overhead  Command   Shared Object     Symbol
# ........  .......   .............     ......
    99.50%  cpu_demo  cpu_demo          [.] busy_user_cpu()
     0.30%  cpu_demo  [kernel.kallsyms] [k] entry_SYSCALL_64
```

- `busy_user_cpu()` 独占 99.5% → 热点一目了然
- O0 编译时，内层循环 `sum += i` 未被优化，就是热点的汇编指令

### 生成火焰图

```bash
# 安装 FlameGraph 工具（一次性）
git clone https://github.com/brendangregg/FlameGraph.git ~/FlameGraph
# 生成火焰图
sudo perf record -F 99 -g -p $(pgrep cpu_demo) -- sleep 30
perf script > out.perf
~/FlameGraph/stackcollapse-perf.pl out.perf > out.folded
~/FlameGraph/flamegraph.pl out.folded > cpu_demo_mode1.svg
```

**火焰图解读**：
- 纵向 = 调用栈深度，横向 = 占用时间
- `busy_user_cpu` → 内部循环占满 CPU → 火焰图几乎是"一根柱子"

### -O0 vs -O2 火焰图对比

```bash
# O0 版
make && ./cpu_demo   # 输入 1
# O2 版
make release && ./cpu_demo_release  # 输入 1
```

| | -O0 (Debug) | -O2 (Release) |
|---|:---:|:---:|
| 热点函数 | `busy_user_cpu()` 可见 | **可能消失**（内联 + DCE） |
| 内层循环 | 汇编可见 | 被向量化/展开，汇编面目全非 |
| `sum += i` | 一清二楚 | `sum` 可能被 DCE 删除（结果未使用） |

**教学点**：O2 下若 `sum` 没有被 `volatile` 保护，`while(true) { sum+=i; }` 中 `sum` 是死存储（dead store），编译器**删除整个循环**。此时 `perf record` 采样到的是一个几乎空闲的进程。

---

## 模式2: syscall 风暴 — 内核态热点

```bash
# 录制（需要能看到内核符号）
sudo perf record -F 99 -g -p $(pgrep cpu_demo) -- sleep 30
sudo perf report --stdio
```

**预期输出**：

```bash
# Overhead  Command   Shared Object      Symbol
# ........  .......   .............      ......
    45.20%  cpu_demo  [kernel.kallsyms]  [k] __x64_sys_write
    20.10%  cpu_demo  [kernel.kallsyms]  [k] __x64_sys_open
    15.30%  cpu_demo  [kernel.kallsyms]  [k] __x64_sys_close
     8.50%  cpu_demo  [kernel.kallsyms]  [k] ext4_file_write_iter
     5.20%  cpu_demo  [kernel.kallsyms]  [k] entry_SYSCALL_64
```

- **热点全在内核态**：`write/open/close` 是 syscall，在内核执行
- 只有 `<5%` 在用户态 → 验证了模式 2 的 syscall 特征
- 解法不是"优化代码"，而是**减少 syscall 次数**（批量化 IO、缓冲区）

### 看内核态火焰图

```bash
sudo perf record -F 99 -g --call-graph dwarf -p $(pgrep cpu_demo) -- sleep 30
perf script > out.perf
~/FlameGraph/stackcollapse-perf.pl out.perf > out.folded
~/FlameGraph/flamegraph.pl --title="syscall storm" out.folded > cpu_demo_mode2.svg
```

火焰图大半深色（内核态），少量彩色（用户态）→ syscall-heavy 程序的典型特征。

---

## 常用参数速查

| 参数 | 作用 |
|------|------|
| `-F 99` | 采样频率 99Hz（`perf record` 是有损降采样） |
| `-g` | 记录调用栈（callchain） |
| `--call-graph dwarf` | 用 DWARF 展开栈帧（O2 时常用，因为 frame pointer 被省略） |
| `--call-graph lbr` | 用硬件 LBR 栈（Intel Haswell+，开销最低） |
| `-a` | 采样系统所有进程 |
| `-p PID` | 只采样指定进程 |
| `-e cycles` | 指定采样事件（默认是 `cycles`） |

---

## 从采样到火焰图的完整流程

```bash
# 1. 录制
sudo perf record -F 99 -g -p PID -- sleep 30
# 2. 文本报告
perf report --stdio
# 3. 导出火焰图
perf script > out.perf
~/FlameGraph/stackcollapse-perf.pl out.perf > out.folded
~/FlameGraph/flamegraph.pl out.folded > flame.svg
# 4. 打开 flame.svg 在浏览器中交互式分析
```

---

## 常见问题

### 栈只有一层 / 一堆 `[unknown]`

| 原因 | 解决 |
|------|------|
| 没编 `-g` | 重编加 `-g` |
| `-O2` 省略 frame pointer | `--call-graph dwarf` 或重编加 `-fno-omit-frame-pointer` |
| 缺 debuginfo 包 | 安装 `debuginfo-install glibc` |
| 内核符号被隐藏 | `sysctl -w kernel.kptr_restrict=0` |

### 采样不到用户态函数（全是 `[unknown]`）

可能是 `perf_event_paranoid` 权限不够：
```bash
sysctl -w kernel.perf_event_paranoid=1   # 允许普通用户采样自己
# 或设为 -1 (完全放开)
```

> **一句话**：`perf record -g` 采样调用栈 → `perf report` 看文字热点 → 火焰图看全景——这是"程序哪里慢"的标准诊断链路。

