课题12:RISC-V后端指令计数统计器
难度:中 | 类型:项目实战 | 源文件:
scratchv/backend/inst_counter.py| 行数:~350 状态:✅ 已完成
概述
解析生成的RISC-V汇编,统计不同类型指令的数量(算术、逻辑、访存、分支等),生成分类统计报告。
理解背景
是什么?
指令计数统计器(inst_counter.py)解析 RISC-V 汇编文件,按类别统计指令数量,并输出文本表格和 HTML 报告。
统计类别:
| 类别 | 包含的指令 | 例子 |
|---|---|---|
| ALU | 算术/逻辑运算 | add, sub, mul, srai, and, or, addi, lui, slt |
| MEM | 内存读写 | lw, sw, lh, sh, flw, fsw |
| BRANCH | 条件分支 | beq, bne, blt, bge |
| JUMP | 无条件跳转 | j, jal, jalr, ret |
| PSEUDO | 伪指令(汇编语法糖) | li, mv, nop, call |
| MISC | 其他/未知 | 无法归类的指令 |
为什么?
编译器优化的核心指标之一就是指令数。你需要知道:
- 各类指令占比:ALU 多 = 计算密集,MEM 多 = 数据密集
- 优化前后对比:优化到底减少了多少条指令?哪一类减少最多?
- ScratchV vs LLVM:两个编译器的指令分布有什么不同?
代码走读
命令行使用
# 先编译 CNN 模型到汇编
python3 scratchv/standalone/onnx_to_riscv_standalone.py models/graph/cnn.onnx \
-o /tmp/cnn_riscv.bin
# 统计汇编文件的指令分布
python3 -m scratchv.backend.inst_counter /tmp/cnn_riscv.s
# 先编译 CNN 模型到汇编
python3 scratchv/standalone/onnx_to_riscv_standalone.py models/graph/cnn.onnx \
-o /tmp/cnn_riscv.bin
# 统计汇编文件的指令分布
python3 -m scratchv.backend.inst_counter /tmp/cnn_riscv.s
实际输出(CNN 模型,887 条指令):
============================================================
RISC-V Instruction Statistics
============================================================
Category Count Percent Bar
------------------------------------------------------------
ALU 604 68.1% ##################################
MEM 92 10.4% #####
BRANCH 48 5.4% ##
JUMP 3 0.3% #
PSEUDO 140 15.8% #######
MISC 0 0.0% #
------------------------------------------------------------
TOTAL 887
------------------------------------------------------------
Per-instruction Breakdown
------------------------------------------------------------
addi 171 (ALU)
add 154 (ALU)
li 108 (PSEUDO)
lui 77 (ALU)
lw 75 (MEM)
mul 71 (ALU)
slt 47 (ALU)
slli 44 (ALU)
bne 43 (BRANCH)
srai 30 (ALU)
sw 17 (MEM)
mv 16 (PSEUDO)
nop 16 (PSEUDO)
slti 5 (ALU)
beq 5 (BRANCH)
and 4 (ALU)
j 2 (JUMP)
auipc 1 (ALU)
ret 1 (JUMP)
生成图表和 HTML 报告
from scratchv.backend.inst_counter import (
count_instructions_file, generate_chart, generate_html_report
)
counts = count_instructions_file("/tmp/cnn_riscv.s")
generate_chart(counts, "/tmp/inst_chart.png") # 需要 pip install matplotlib
generate_html_report(counts, "/tmp/inst_report.html", title="CNN Model")
多文件对比
from scratchv.backend.inst_counter import compare_files
# 对比多个汇编文件
result = compare_files(["/tmp/cnn_riscv.s", "/tmp/llvm_output.s"])
# result.counts → {"file1": Counter(...), "file2": Counter(...)}
# result.diffs → {"file2": {"ALU": +50, "MEM": -10, ...}}
# result.to_dataframe() → pandas DataFrame
Python API 基础用法
from scratchv.backend.inst_counter import count_instructions_file, format_table
counts = count_instructions_file("/tmp/cnn_riscv.s")
print(format_table(counts))
动手练习
练习 1: 分析 CNN 模型的指令分布
from scratchv.backend.inst_counter import (
count_instructions_file, generate_chart, generate_html_report
)
counts = count_instructions_file("/tmp/cnn_riscv.s")
generate_chart(counts, "/tmp/inst_chart.png") # 需要 pip install matplotlib
generate_html_report(counts, "/tmp/inst_report.html", title="CNN Model")
from scratchv.backend.inst_counter import compare_files
# 对比多个汇编文件
result = compare_files(["/tmp/cnn_riscv.s", "/tmp/llvm_output.s"])
# result.counts → {"file1": Counter(...), "file2": Counter(...)}
# result.diffs → {"file2": {"ALU": +50, "MEM": -10, ...}}
# result.to_dataframe() → pandas DataFrame
Python API 基础用法
from scratchv.backend.inst_counter import count_instructions_file, format_table
counts = count_instructions_file("/tmp/cnn_riscv.s")
print(format_table(counts))
动手练习
练习 1: 分析 CNN 模型的指令分布
from scratchv.backend.inst_counter import count_instructions_file, format_table
counts = count_instructions_file("/tmp/cnn_riscv.s")
print(format_table(counts))
练习 1: 分析 CNN 模型的指令分布
用 make bench-cnn 生成汇编,统计各类指令占比。哪类最多?为什么?
练习 2: 优化前后对比
对同一段代码,分别统计优化前和优化后的指令分布,计算减少了多少条、哪些类别变化最大。
练习 3: 添加新的指令分类
在 _OPCODE_CATEGORIES 字典中添加 RISC-V F/D 扩展(浮点)指令的映射。
常见坑
| 坑 | 说明 |
|---|---|
| 伪指令统计 | li a0, 5 统计为 PSEUDO 类别,不会展开为真指令后再统计 |
| matplotlib 依赖 | 生成图表需要 pip install matplotlib,不是必需功能 |
| 指令识别遗漏 | 某些特殊格式的汇编行可能无法提取操作码(如带复杂寻址模式的指令) |