name: branch-predictability-triage description: "用于分析分支 PC 对应的 ELF / 函数 / 源码语义,并判断该分支更像是语义上天然难预测,还是更像预测器没有学好。适用于 SPEC06 checkpoint、benchmark ELF、topMispredictsByBranch.csv、单个 branch PC 归因。"
分支可预测性归因技能
何时使用
- 你手里有一个或多个 branch PC,想知道它们属于:
- benchmark 主体
- runtime / toolchain(如
libgcc、glibc、jemalloc) bbl/ kernel / 高地址运行时
- 你想把 branch PC 尽量对应到:
- ELF
- 函数名
- 源码文件 / 代码块
- 你想判断一条分支更像:
- 语义上天然难预测
- 结构上本应较容易预测,但 predictor 没抓住
- 你想分析 SPEC06 切片,例如:
- 新 profile:
/nfs/home/share/checkpoints_profiles/.../checkpoint-0-0-0 - 老 profile:
/nfs/share/zyy/spec06_rv64gcb_O3_20m_gcc12.2.0-intFpcOff-jeMalloc/...
- 新 profile:
目标
输出一份面向分支预测分析的结论,而不是单纯做地址翻译。最终结论至少要回答:
- 这条 branch PC 属于哪类代码。
- 能否对应到 benchmark 自身源码。
- 这条分支的控制流模式是什么。
- 更像是“天然难预测”还是“predictor 还有提升空间”。
输入优先级
优先收集以下信息:
- branch PC 列表
- 切片名或 benchmark 名
- 切片根目录
- 可能的
topMispredictsByBranch.csv/topMisrateByBranch.csv - 如有,用户本地 benchmark 源码树
如果用户只给了 PC,没有给切片名,也可以先按地址区间做粗分类。
地址分类规则
先判断 PC 是否属于 benchmark ELF 的装载范围,不要一上来就找源码行。
A. 低地址、落在 benchmark ELF .text
常见表现:
0x10000左右开始的静态可执行映像llvm-symbolizer/nm能解析到 benchmark 函数
处理策略:
- 优先认为是 benchmark 主体或静态链接进 benchmark 的 runtime/helper
B. 高地址,如 0x8000xxxx
常见表现:
- 不落在 benchmark ELF 的 LOAD 段
- 更像
bbl/ opensbi / kernel / 其他 runtime
处理策略:
- 不要强行拿 benchmark ELF 解
- 先说明该地址大概率不属于 benchmark 主体
- 如果没有对应 runtime ELF,只能停在“非 benchmark 代码”这一层
仓库内常见路径
新 profile
- checkpoint 根目录:
/nfs/home/share/checkpoints_profiles/<profile>/checkpoint-0-0-0 - ELF 目录:
/nfs/home/share/checkpoints_profiles/<profile>/elf
常见映射:
gcc_typeck/gcc_scilab/gcc_expr2/gcc_200->elf/gccperlbench_splitmail/perlbench_diffmail->elf/perlbenchbzip2_*->elf/bzip2gobmk_*->elf/gobmkastar_*->elf/astargamess_*->elf/gamessmcf->elf/mcfsjeng->elf/sjeng
老 profile
- 根目录:
/nfs/share/zyy/spec06_rv64gcb_O3_20m_gcc12.2.0-intFpcOff-jeMalloc - benchmark ELF:
elf/<bench>_base.riscv64-linux-gnu-gcc12.2.0 - 运行镜像:
bin/*-bbl-linux-spec.bin
注意:
bin/*-bbl-linux-spec.bin往往不是 ELF,不能直接addr2line- 真正可用于静态语义分析的通常是
elf/下的 benchmark ELF
本地源码树
常见 SPEC2006 源码路径:
/nfs/home/yanyue/tools/cpu2006_analyze/benchspec/CPU2006
例如:
400.perlbench/src403.gcc/src429.mcf/src458.sjeng/src
推荐工具
优先使用:
filereadelf -Sreadelf -Wlnm -nllvm-symbolizerllvm-objdump -d --line-numbers --sourcerg
必要时使用:
gdb -batch -ex 'info line *ADDR'readelf --debug-dump=decodedline
不建议默认依赖系统自带 addr2line,因为某些 RISC-V + DWARF 组合下它可能只能给函数名,不能稳定给源码行。
标准分析流程
第一步:确认 ELF 是否可用
先检查:
file <elf>
readelf -S <elf> | rg 'debug|symtab|strtab'
readelf -Wl <elf>
目标:
- 确认是否是 ELF
- 是否带
debug_info - 代码装载地址范围是什么
第二步:判断 PC 是否属于该 ELF
如果 PC 明显不在 LOAD 段范围内:
- 直接标记为“非该 benchmark ELF 主体地址”
- 不要继续做伪映射
第三步:先到函数级
优先拿到函数名:
llvm-symbolizer --obj=<elf> 0xPC
nm -n <elf> | rg '<附近符号>'
如果只能到函数名,也不要停。函数级 + 本地源码通常已经足够做语义分析。
第四步:查看函数内分支上下文
llvm-objdump -d --line-numbers --source \
--start-address=<pc附近起点> \
--stop-address=<pc附近终点> \
<elf>
重点看:
- 比较指令前的 load / and / shift / compare
- branch 是:
beqz/bnezblt/bge- 循环回边
- 早退条件
- “刷新最大值”类选择分支
第五步:映射到本地源码块
如果 line table 不够稳定:
- 用函数名在本地源码树里找定义
- 再用汇编语义对到源码块
示例:
rg -n '^.*\\bpush_slidE\\b\\s*\\(' /nfs/home/yanyue/tools/cpu2006_analyze/benchspec/CPU2006/458.sjeng/src/*.c
这一步的目标不是强行制造“精确某一行”,而是定位到:
- 哪个函数
- 哪个
if/else/loop - 它的输入依赖是什么
第六步:判断分支类型
每条分支至少归到以下一种:
loop-exitguard / fastpathpredicate-resultmax/min updatepointer/null/empty checkstate-machine / parser / regexruntime/helper
第七步:输出预测性结论
结论至少包含:
- 该分支更像“结构型易预测”还是“语义型难预测”
- 如果 predictor 表现差,更该怀疑:
- predictor 模型 / 历史建模 / alias / 容量
- 还是输入分布本身导致的不可规整
预测性判断准则
下面是默认启发式,不是绝对规则。
通常偏容易预测
for/while循环退出条件- 连续扫描直到边界/空值/哨兵值
- 长度下界检查
- 空指针 / 空格 /
npiece/frame/ null-check - 稳定模式位,例如
captures、mode、flag长时间不变 - 明显偏置的错误路径 / 稀有路径
常见表现:
- 连续若干次 taken,然后一次 not-taken
- 连续若干次 not-taken,然后一次 taken
- 同一 phase 下高度偏置
如果这类分支 mispredict 很高,更值得怀疑:
- predictor 没学住简单结构
- 同一 PC 混入太多上下文
- 表项别名或容量冲突
通常更难预测
- regex / parser / symbol-table / search-state 驱动的判断
if (value > best)这种“刷新最大值/最小值”类分支- 依赖
load出来的动态值,再做分类/比较 - 依赖输入真假分布的 filter / predicate 结果
- 依赖多重全局状态的启发式判断
- 匹配成功/失败、查表命中/未命中、搜索剪枝命中/未命中
常见表现:
- 同一 PC 在不同 phase 下行为变化很大
- taken ratio 接近中间值
- 结果高度依赖输入内容或状态机位置
如果这类分支 mispredict 很高,不一定说明 predictor 有明显问题;可能是语义上本来就更难。
典型案例模板
案例 A:滑动子走子生成
类似:
board[target] == npieceboard[target] != frame
判断:
- 这是典型扫描型分支
- 通常结构规整,偏容易预测
- 如果预测差,优先怀疑 predictor 没把 ray 长度/phase 模式学好
案例 B:搜索排序中的“刷新最大值”
类似:
if (move_ordering[i] > best)
判断:
- 这是数据相关分支
- 依赖 move ordering 分布
- 比 loop-exit 明显更难
- 预测差未必是 predictor bug
案例 C:regex / match 成败
类似:
if (!s) goto nope;if (CALLREGEXEC(...))
判断:
- 强依赖输入文本、状态、匹配位置
- 通常比长度检查更难预测
输出格式建议
对每个 branch PC,建议输出以下字段:
pcbenchmarkelfbelongs_tobenchmarkruntime/toolchainbbl/high-address
functionsource_candidatesemantic_patternpredictabilityeasymediumhard
whytage_interpretationmore_like_predictor_issuemore_like_semantically_hardmixed
使用时的注意事项
- 不要把“无法精确到行号”误判为“完全无法分析”。
- 对 benchmark 主体,函数级定位 + 本地源码块通常已经足够做预测性判断。
- 对
0x8000xxxx这类地址,先排除 runtime/bbl,再谈源码。 - 对
libgcc/glibchelper,要明确告诉用户:这不是 benchmark 自身算法分支。 - 如果用户的目标是比较 predictor 设计优劣,优先找:
- 结构上本应好预测,但 mispredict 很高的分支
- 如果用户的目标是解释 workload 本身难度,优先找:
- 语义上强数据相关的分支
默认结论风格
回答时优先给出:
- 该 PC 属于什么代码
- 它大致对应哪段源码逻辑
- 它属于哪种分支模式
- 我为什么判断它偏 easy / hard
- 我更倾向把责任归到 predictor 还是 workload 语义