﻿# cpu-demo — perf stat 基础指标分析指南

> [cpu-demo/main.cpp](/demos/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 utilized
```

- `task-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 5
```

```bash
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](/concepts/tools/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-switches
```

- `task-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 行就完成了性能分析的"体检"。

