课题6:编译器性能测试套件

难度:中 | 类型:项目实战 | 源文件benchmarks/bench_runner.py | 行数:~400 状态:✅ 已完成


概述

设计一组基准测试程序(DSL用例),自动化执行编译、模拟运行、对比预期输出,生成性能报告。该套件用于持续验证编译器的正确性和性能变化。


理解背景

是什么?

性能基准套件(Benchmark Suite)自动化测试 ScratchV 编译器的正确性性能。它收集了 23 个 DSL 测试用例,覆盖算术、神经网络算子、控制流等各类场景,每次运行自动生成 JSON/HTML/Markdown 报告。

为什么?

编译器每次改动都可能引入 bug 或性能衰退。手工测试既不全面也不可靠。自动化的基准测试套件让你: - 回归检测:改了代码后跑一遍,立即知道有没有破坏现有功能 - 性能追踪:记录每次运行的时间,发现性能衰退 - CI 集成:每次 push 自动运行,阻止有问题的代码合入

核心概念

测试用例格式

每个测试用例最多 2 个文件(放在 benchmarks/cases/):

001_simple_add.dsl         DSL 源代码必需
001_simple_add.expected    期望输出可选

DSL 语法要点

数字可以直接内联,变量名自动解析:

# 正确写法
c = add(a, b)              # a, b 作为输入变量
t1 = add(2.0, 3.0)         # 数字直接写
return c

# 错误写法(没有 const 操作符!)
a = const(3)                # ❌ 不支持

支持的算子:add, sub, mul, div, neg, exp, relu, gelu, dot, matmul, softmax, maxpool

23 个用例覆盖范围
类别 数量 示例
算术 5 add, mul, sub, div, chained
神经网络 6 relu, gelu, softmax, matmul, dot, maxpool
控制流 7 for-loop, if/else, while, nested
复杂场景 3 NN pipeline, large chain
常量 1 constant propagation

报告格式
格式 用途
终端输出 开发时快速查看
JSON CI dashboard 数据源
HTML 可视化展示
Markdown 文档、PR review

代码走读

运行全部测试
# 基础运行(指定用例目录)
python3 benchmarks/bench_runner.py benchmarks/cases

# 生成 JSON + HTML 报告
python3 benchmarks/bench_runner.py benchmarks/cases \
    --output-json /tmp/dsl_bench.json \
    --output-html /tmp/dsl_bench.html

实际输出(23/23 PASS):

Discovered 23 test case(s) in benchmarks/cases
------------------------------------------------------------
  [1/23] 001_simple_add ... PASS (0.008s parse, 2 inst)
  [2/23] 002_simple_mul ... PASS (0.000s parse, 2 inst)
  ...
  [23/23] 023_large_chain ... PASS (0.000s parse, 7 inst)

用 Makefile 一键运行
make bench    # 等价于上面的完整流程

添加新测试用例
cat > benchmarks/cases/024_my_test.dsl << 'EOF'
# 我的自定义测试: 10 + 20 - 5
a = add(10, 20)
b = sub(a, 5)
return b
EOF

echo "25" > benchmarks/cases/024_my_test.expected

# 运行确认
python3 benchmarks/bench_runner.py benchmarks/cases

💡 关键:数字直接内联使用(如 10, 20),不需要 const() 包装。期望输出文件是纯文本,末尾不要有多余空行。

Python API 用法
from benchmarks.bench_runner import BenchmarkRunner

# 创建 runner
runner = BenchmarkRunner(
    test_dir="benchmarks/cases",
    timeout=30.0,
    verbose=True,
)

# 发现测试用例
cases = runner.discover_cases()
print(f"Found {len(cases)} test cases")

# 运行全部(返回 BenchmarkReport)
report = runner.run_all()
report.print_summary()

# 生成报告
report.save_json("/tmp/results.json")
report.save_html("/tmp/results.html")
report.save_markdown("/tmp/results.md")

CI 回归检测
# 生成基线
python3 benchmarks/bench_runner.py benchmarks/cases --output-json /tmp/baseline.json

# 修改代码后...
python3 benchmarks/bench_runner.py benchmarks/cases --output-json /tmp/current.json

# 对比通过率
python3 -c "
import json
baseline = json.load(open('/tmp/baseline.json'))
current = json.load(open('/tmp/current.json'))
if current['pass_rate'] < baseline['pass_rate']:
    print('REGRESSION DETECTED!')
    exit(1)
else:
    print('OK: pass_rate stable')
"

动手练习

练习 1: 添加一个新测试用例

为你正在开发的编译器功能写一个测试用例,跑通并确认通过。

练习 2: 故意引入 bug

修改代码引入一个 bug(比如把 ADD 映射成 SUB),跑 benchmark 看看哪些测试会失败。

练习 3: 分析性能趋势

连续跑 3 次 benchmark,对比 JSON 输出,看编译时间是否有波动。


常见坑
说明
没有 const 操作符 DSL 不支持 a = const(3),直接用数字 a = add(3, 5)
expected 文件格式 .expected 文件应该是纯文本,末尾不要有多余换行
超时 某些测试可能死循环,BenchmarkRunner 有 timeout 保护

进阶阅读