性能分析与优化¶
性能分析的目标不是“找到一个看起来相关的计数器”,而是构造一条证据链:
从三个基准数开始¶
任何实验先记录:
minstret 是退休的架构指令数,不等于内部微操作数、发射数或执行数。向量拆分、
融合、错误路径执行和 replay 都会让这些口径不同。
香山的事件路径¶
源码中大量模块使用 XSPerfAccumulate 记录事件,并通过 generatePerfEvent
形成性能事件集合。PFEvent 为 mhpmevent3 起的事件选择 CSR 提供状态,
HPM counter 再对选中事件计数。
关键入口:
PFEvent.scalaNewCSR.scala- 在源码中搜索
XSPerfAccumulate和generatePerfEvent
事件编号不是跨版本 ABI
模块参数、事件拼接顺序和配置可能改变事件映射。不要把一次生成中的 raw event
ID 永久写进脚本;应保存对应 commit、CONFIG 和该次生成的事件清单。
Top-down:先分大类¶
当前源码有两类互补的归因信息:
TopDownCounters:前端 bubble、ROB/LQ/SQ、freelist、dispatch 等更细的 stall reason;TopDownGen:用 issued uops、LQ/SQ 状态与 L1/L2/L3 miss 信号形成执行和 内存 stall 事件。
例如 TopDownGen 中的内存层级归因是逐层收窄:
实现见
TopDownGen.scala#L13-L52。
这是一种实现定义的归因启发式,不等价于“该层 miss 独占造成了全部停顿周期”;
仍需结合队列占用、依赖链和实验验证。
从现象到结构¶
| 现象 | 第一假设 | 需要交叉验证 |
|---|---|---|
| 前端 bubble 高 | BPU 错误、ICache/ITLB miss、取指碎片 | 各类 redirect、FTQ/IBuffer 占用、代码布局 |
| ROB 经常满 | 头部有长延迟操作,退休被堵住 | ROB head 类型、cache/TLB miss、div/FP/vector |
| IQ 满但发射少 | 操作数依赖或端口/FU 冲突 | ready 位、wakeup、FU busy、写回冲突 |
| LQ/SQ 满 | miss/replay/commit drain 不足 | MSHR、前递、违例、SBuffer、fence |
| L1 miss 高但 IPC 尚可 | MLP/预取隐藏了延迟 | outstanding miss、prefetch accuracy、ROB head |
| L1 miss 不高但 load 慢 | TLB、bank conflict、前递或 replay | TLB event、DCache bank、LSQ replay 原因 |
| 分支数不多却前端慢 | 少量高代价、难预测分支 | 每千指令 mispredict 与单次恢复代价 |
| 多核锁竞争严重 | cache line ping-pong | RFO/ownership、snoop、store/fence、NUMA/拓扑 |
仓库自带 Top-down 工具¶
scripts/top-down 可以读取 checkpoint 的统计输出,按前端、后端、访存和自定义
维度生成 CSV 与图表。主入口:
cd scripts/top-down
python3 top_down.py \
--base-stat-dir <base_perf_report> \
--json <checkpoint.json>
对比两个版本时必须提供相应 issue width:
python3 top_down.py \
--base-stat-dir <base> \
--ref-stat-dir <ref> \
--json <checkpoint.json> \
--base-issue 8 \
--ref-issue 8 \
--base-label Base \
--ref-label Change
以实际生成配置为准,不要机械照抄示例中的宽度。完整参数见
scripts/top-down/README.md。
一套可复用的分析流程¶
1. 固定实验¶
- 固定 commit、config、频率/仿真模型、编译器和 workload 输入;
- 预热 cache/分支预测器或明确冷启动口径;
- 至少重复多次,报告中位数与离散度;
- 同时保留 raw counter,避免只剩归一化图。
2. 用差值而不是绝对值¶
比较 baseline 与 change:
Δcycles
Δinstructions
Δstall_category
event_per_kinst = 1000 × event / instructions
Δevent_per_kinst = change_event_per_kinst - base_event_per_kinst
总量、每千指令事件和每周期事件回答不同问题。代码变更若改变动态指令数,只看 raw miss 数通常会误判。
3. 构造可证伪假设¶
差的表述:
IPC 下降可能和 cache 有关。
好的表述:
这次布局修改提高了同一热循环的 ICache miss/kinst;若它是主因,则恢复旧布局 应同时降低 ICache miss、前端 bubble 和 cycles,而退休指令数基本不变。
4. 回到源码¶
找到事件的产生条件,确认:
- 是 pulse 计次、周期占用,还是 PopCount 增量;
- 是否包含错误路径、预取、replay 或多个端口;
- enable/disable 条件和 counter 位宽;
- 事件名称到 HPM 选择值的生成顺序。
5. 只改一个变量¶
用代码布局、预取开关、数据集大小、依赖链长度或并发线程数构造对照。若无法让 目标结构的压力单调变化,就还没有真正验证归因。
优化优先级¶
通常按“损失周期 × 可改善比例 × 风险”排序,而不是按某个事件的绝对值排序。 高 miss rate 若已被 MLP 完全隐藏,未必比一个低频但每次清空大窗口的错误预测 更值得优先处理。