Skip to content

Commit 19bbcd4

Browse files
committed
Add benchmark Docs
1 parent 6ef7dde commit 19bbcd4

1 file changed

Lines changed: 295 additions & 0 deletions

File tree

Lines changed: 295 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
# 寄存器分配 Benchmark 设计文档
2+
3+
## 1. 概述
4+
5+
对 ScratchV 的**线性扫描寄存器分配器**`LinearScanAllocator`)进行正确性与性能评估,并在 CNN 路径上与 LLVM 后端做指令数及寄存器溢出的交叉对比。
6+
7+
**设计目标**
8+
9+
- **正确性**:验证无溢出 / 有溢出两种场景的分配结果合法(无未解析 vreg、有效 opcode)
10+
- **性能**:测量分配耗时(均值 / 标准差)、活跃区间峰值压力
11+
- **可对比**:每项输出统一的 `reg_spill_count` 指标,支持回归对比和筛选
12+
- **跨后端对比**:同一 ONNX 模型经 ScratchV 和 LLVM 两条路径编译,对比静态指令数、opcode 类别分布、溢出/帧操作数量
13+
14+
入口:`benchmarks/test_regalloc/run_benchmark.py`
15+
单文件运行:直接执行 `bench_simple.py` / `bench_dense.py` / `bench_cnn.py`
16+
17+
---
18+
19+
## 2. 架构
20+
21+
```
22+
benchmarks/test_regalloc/
23+
├── __init__.py
24+
├── bench_utils.py 共享常量(opcode 分类、callee-saved 集合)、llvmlite IR→RISC-V
25+
├── bench_simple.py Benchmark 1 — 无溢出正确性
26+
├── bench_dense.py Benchmark 2 — 溢出正确性
27+
├── bench_cnn.py Benchmark 3 — CNN 集成 + LLVM 对比
28+
└── run_benchmark.py 运行器:汇总 3 路输出 → JSON / HTML / Markdown 报告
29+
```
30+
31+
### 2.1 统一接口
32+
33+
每个 benchmark 文件导出:
34+
35+
```python
36+
def run_bench(...) -> dict:
37+
"""返回统一结构的统计 dict,必需键见 §4。"""
38+
```
39+
40+
运行器遍历三个 `run_bench()`,收集 dict 生成报告。
41+
42+
### 2.2 数据流
43+
44+
```
45+
┌──────────────────┐
46+
│ run_benchmark.py │
47+
└──────┬───────────┘
48+
┌─────────────┼─────────────┐
49+
▼ ▼ ▼
50+
bench_simple bench_dense bench_cnn
51+
run_bench() run_bench() run_bench()
52+
│ │ │
53+
▼ ▼ ▼
54+
LinearScanAllocator ... LinearScanAllocator
55+
.allocate() .allocate()
56+
.report() / to_dict() .report() / to_dict()
57+
│ │ │ │
58+
▼ ▼ ▼ ▼
59+
{dict} {dict} {dict} {LLVM dict}
60+
│ │ │ │
61+
└──────────────┴─────────┴─────────┘
62+
63+
64+
JSON / HTML / MD 报告
65+
```
66+
67+
---
68+
69+
## 3. 三项 Benchmark
70+
71+
### 3.1 Benchmark 1 — 简单算术(无溢出)
72+
73+
**文件**`bench_simple.py`
74+
**目的**:验证物理寄存器充足时分配器产生零溢出。
75+
76+
| 参数 ||
77+
|------|-----|
78+
| 虚拟寄存器 | 5 个(`v0``v4`|
79+
| 物理寄存器池 | 8 个(`r0``r7`|
80+
| 指令数 | 10 条(随机 add/sub/mul/and/or) |
81+
| 块生成 | `_gen_block(num_insts=10, num_vregs=5)` |
82+
| 断言 | `reg_spill_count == 0``valid = True` |
83+
84+
85+
86+
### 3.2 Benchmark 2 — 密集计算(触发溢出)
87+
88+
**文件**`bench_dense.py`
89+
**目的**:人为制造高寄存器压力,迫使分配器溢出,验证溢出代码生成正确。
90+
91+
| 参数 ||
92+
|------|-----|
93+
| 虚拟寄存器 | 30 个(`v0``v29`|
94+
| 物理寄存器池 | 5 个(`r0``r4`|
95+
| 指令数 | 80 条(30 条 define + 50 条交叉引用) |
96+
| 块生成 | 阶段 1: 逐个定义 vreg → 创建长 live range;阶段 2: 随机交叉引用保持活跃 |
97+
| 断言 | `reg_spill_count > 0``valid = True` |
98+
99+
100+
101+
### 3.3 Benchmark 3 — CNN 集成 + LLVM 对比
102+
103+
**文件**`bench_cnn.py`
104+
**目的**:通过真实 ONNX 模型编译流水线验证分配器,并与 LLVM 后端做指令数和溢出对比。
105+
106+
**ScratchV 编译流水线**
107+
108+
```
109+
ONNXParser → IR Program (17 ops)
110+
→ ConstantFolder → DeadCodeEliminator
111+
→ InstructionSelector → MachineInstr (57, with vregs)
112+
→ block_from_machine_instrs → [LsInstruction]
113+
→ LinearScanAllocator.allocate()
114+
→ get_allocated_code() → RISC-V 伪指令
115+
→ _validate_asm() # 检查无未解析 vreg、合法 opcode
116+
```
117+
118+
**LLVM 对比流水线**
119+
120+
```
121+
convert_onnx_to_llvm(model) → LLVM IR (866K lines, 183MB)
122+
→ llvmlite IR → RISC-V asm → 静态指令数 + opcode 分布
123+
→ 汇编启发式 → 溢出 slot / 帧操作 近似统计
124+
```
125+
126+
| 参数 ||
127+
|------|-----|
128+
| 模型 | `models/graph/cnn.onnx`(可 CLI 覆盖) |
129+
| IR 指令 | 17 条(3×conv + 3×relu + 3×maxpool + 2×gemm + sigmoid + 2×reshape) |
130+
| 物理寄存器 | `_INT_REGS`(28 个) |
131+
| ScratchV 输出 | ~57 条伪指令(mv/mul/add/slt/bnez…) |
132+
| LLVM 输出 | ~1099 条(RV64FD O2,真实循环展开) |
133+
| 断言 | `asm_valid == True` |
134+
135+
> **注意**:ScratchV 侧输出 57 条**伪指令**(conv/maxpool 等语义级操作由仿真器实现),LLVM 侧输出 1099 条**自包含机器指令**(每个 conv 展开为 5 重嵌套循环的完整 RISC-V 指令序列)。`instr_ratio_fd ≈ 23.89x` 反映的是抽象层级差异而非优化能力差距,因此还提供 opcode **类别分布**作为跨层级可比指标。
136+
137+
---
138+
139+
## 4. 指标规范
140+
141+
### 4.1 所有 benchmark 通用键
142+
143+
|| 类型 | 来源 | 说明 |
144+
|----|------|------|------|
145+
| `mean_s` | `float` | `perf_counter` 均值 | 单次分配耗时(秒) |
146+
| `stdev_s` | `float` | `stdev` | 耗时标准差 |
147+
| `vreg_count` | `int` | `len(alloc.alloc_map)` | 已分配的虚拟寄存器数 |
148+
| `spills` | `int` | `len(alloc._spill_slots)` | 溢出 slot 数(别名) |
149+
| `reg_spill_count` | `int` | 同上 | **统一溢出指标键**(接口规范) |
150+
| `peak_active` | `int` | `alloc.peak_active` | 峰值同时活跃的物理寄存器数 |
151+
| `asm_lines` | `int` | `len(code.splitlines())` | 汇编输出行数 |
152+
| `valid` | `bool` |`run_bench()` 设置 | 该项是否通过断言 |
153+
154+
### 4.2 Benchmark 特有键
155+
156+
**bench_dense**
157+
158+
|| 说明 |
159+
|----|------|
160+
| `reloads` | `lw ... # reload` 注释行数 |
161+
162+
**bench_cnn (ScratchV 侧)**
163+
164+
|| 来源 | 说明 |
165+
|----|------|------|
166+
| `vreg_total` | ONNXParser→isel | 编译流水线中出现的 vreg 总数 |
167+
| `ir_inst_count` | Program 指令计数 | IR 层操作数(=17) |
168+
| `machine_instrs` | `len(machine)` | MachineInstr 数量(=57) |
169+
| `sv_static_instrs` | `count_riscv_instrs()` | 汇编指令数(~46,不含标签/注释) |
170+
| `sv_cats` | `count_riscv_instrs()` | opcode 原始计数 |
171+
| `sv_cat_buckets` | `_op_categories()` | 6 类汇总(ALU/Load/Store/Branch/Mul/Other) |
172+
| `asm_errors` | `_validate_asm()` | 未解析 vreg / 未知 opcode 列表 |
173+
| `asm_valid` | `len(asm_errors) == 0` | 汇编合法性 |
174+
| `greedy_time_s` | Greedy allocator | Greedy分配器耗时(baseline) |
175+
| `greedy_out_instrs` || Greedy分配器输出指令数 |
176+
177+
**bench_cnn (LLVM 对比侧)**
178+
179+
|| 说明 |
180+
|----|------|
181+
| `llvm_im_instrs` | LLVM RV64IM 静态指令数 |
182+
| `llvm_fd_instrs` | LLVM RV64FD 静态指令数 |
183+
| `instr_ratio_fd` | `llvm_fd_instrs / max(sv_static_instrs, 1)` |
184+
| `llvm_fd_cats` | opcode 原始计数 |
185+
| `llvm_fd_cat_buckets` | 7 类汇总(+ Stack) |
186+
| `llvm_spill_slots` | 4 字节 sp 访问 ÷ 2(**近似**|
187+
| `llvm_frame_save` | `sd` 到 callee-saved 的计数 |
188+
| `llvm_frame_restore` | `ld` 到 callee-saved 的计数 |
189+
190+
### 4.3 `reg_spill_count` 规范
191+
192+
- **经过 regalloc 的路径**:直接取自 `alloc._spill_slots` 长度 → 精确值
193+
- **不经过 regalloc 的路径**:LLVM 侧 `reg_spill_count` 是本路径的 ScratchV 精确值(0);LLVM 近似溢出独立为 `llvm_spill_slots`,不污染统一键
194+
- **降级路径**:当 libLLVM 不可用时,`llvm_fd_instrs`/`llvm_spill_slots` 等键不存在于 dict 中,报告渲染 fallback 到 `"-"`
195+
196+
---
197+
198+
## 5. LLVM 溢出统计
199+
200+
由于 libLLVM 缺少 regalloc pass 统计入口,LLVM 侧无法获取精确的寄存器溢出计数,通过汇编层面识别特定模式统计溢出:
201+
202+
| 形态 | 正则 / 判定 | 含义 |
203+
|------|------------|------|
204+
| 帧保存 | `sd <callee-saved>, N(sp)` | prologue 保存 callee-saved 寄存器 |
205+
| 帧恢复 | `ld <callee-saved>, N(sp)` | epilogue 恢复 callee-saved 寄存器 |
206+
| 溢出 | `sw/lw/fsw/flw <reg>, N(sp)` | 寄存器值被 spill→reload(4 字节) |
207+
208+
**callee-saved 集合**(RV64 ABI):`ra, fp, s0–s11, fs0–fs11`
209+
210+
---
211+
212+
## 6. 输出格式
213+
214+
### 6.1 终端
215+
216+
```
217+
============================================================
218+
ScratchV — Register Allocation Benchmark Suite
219+
============================================================
220+
1. Simple: reg_spill_count=0, mean=0.019ms ✓
221+
2. Dense: reg_spill_count=15, mean=0.084ms ✓
222+
3. CNN: reg_spill_count=0, mean=0.052ms ✓
223+
LLVM: RV64FD=1099 instrs (23.89x vs ScratchV 46)
224+
225+
Total: 25173.6ms PASS
226+
227+
JSON report: /tmp/rg_spill.json
228+
HTML report: /tmp/rg_spill.html
229+
Markdown: /tmp/rg_spill.md
230+
```
231+
232+
### 6.2 JSON
233+
234+
标准化结构,`_` 前缀字段(如 asm 文本、allocator 实例)被排除:
235+
236+
```json
237+
{
238+
"timestamp": "2026-08-09T...",
239+
"total_time_s": 25.17,
240+
"repeats": 3,
241+
"results": {
242+
"1. Simple Arithmetic": {
243+
"mean_s": 1.9e-05, "reg_spill_count": 0, "peak_active": 5,
244+
"asm_lines": 10, "valid": true
245+
},
246+
"2. Dense Computation": {
247+
"mean_s": 8.4e-05, "reg_spill_count": 15, "peak_active": 14,
248+
"asm_lines": 179, "reloads": 61, "valid": true
249+
},
250+
"3. CNN Integration": {
251+
"mean_s": 5.2e-05, "reg_spill_count": 0,
252+
"sv_static_instrs": 46, "llvm_fd_instrs": 1099,
253+
"instr_ratio_fd": 23.89, "llvm_spill_slots": 87,
254+
"llvm_frame_save": 70, "llvm_frame_restore": 70,
255+
"llvm_fd_cat_buckets": {"ALU": 346, "Load": 98, ...},
256+
"asm_valid": true, "valid": true
257+
}
258+
}
259+
}
260+
```
261+
262+
### 6.3 HTML / Markdown 汇总表
263+
264+
| Benchmark | Mean(ms) | Std(ms) | Vregs | Spills | Peak | Reloads | Asm | LLVM-FD | Ratio | LLVM-Spill | Valid |
265+
|-----------|----------|---------|-------|--------|------|---------|-----|---------|-------|------------|-------|
266+
| 1. Simple Arithmetic | 0.019 | 0.011 | 5 | 0 | 5 | - | 10 | - | - | - ||
267+
| 2. Dense Computation | 0.084 | 0.016 | 30 | 15 | 14 | 61 | 179 | - | - | - ||
268+
| 3. CNN Integration | 0.052 | 0.015 | 30 | 0 | 11 | - | 57 | 1099 | 23.89 | 87 ||
269+
270+
---
271+
272+
## 7. 使用方式
273+
274+
```bash
275+
# 运行全部 3 项 benchmark,生成三格式报告
276+
python benchmarks/test_regalloc/run_benchmark.py \
277+
--repeats 30 \
278+
--output-json report.json \
279+
--output-html report.html \
280+
--output-md report.md
281+
282+
# 单独运行某项
283+
python benchmarks/test_regalloc/bench_simple.py --repeats 100
284+
python benchmarks/test_regalloc/bench_dense.py --repeats 50
285+
python benchmarks/test_regalloc/bench_cnn.py \
286+
--cnn-path models/graph/cnn.onnx --repeats 30
287+
```
288+
289+
---
290+
291+
## 8. TODO
292+
293+
1. **优化报告输出格式**
294+
2. **将通用的Benchmark组件进一步抽象到`bench_utils.py`当中**
295+
3. **目前ONNX模型的编译路径指令选择方面无法完全正确生成算子的汇编指令,与LLVM后端对比不公平**

0 commit comments

Comments
 (0)