做性能优化时,我们经常会遇到一个尴尬的问题:工具能给出大量事实,但很少直接告诉我们“下一步应该看哪里”。
传统 profiling
工具通常擅长回答局部问题。例如,火焰图可以告诉我们哪些函数累计时间长,perf report
可以告诉我们哪些指令或符号采样多,Chrome Trace
可以展示一次请求中各个阶段的耗时。但是这些信息往往停留在不同视角里:时间线是一种视角,指令流是一种视角,调用栈又是另一种视角。真正做优化时,人需要在这些视角之间来回切换,手工判断一个函数到底是“调用次数太多”“单次太慢”“偶发抖动很大”“IPC
太低”,还是“热点集中在函数内部某几条动态路径上”。
ALTPT 的设计目标就是把这一步尽量自动化:它不是只把 trace 文件打印成表格,而是尝试从 trace 中提炼出更接近优化决策的问题分类。也就是说,ALTPT 关注的不是“trace 里有什么”,而是“trace 暗示了什么性能 antipattern”。
它试图把一次分析拆成三层:
慢的分类器:先判断慢来自哪里。是调用次数太多,平均耗时太高,尾部延迟太大,还是 CPU 执行效率偏低。
慢的解释器:再解释为什么慢。一个高耗时函数可能是因为子调用过多,也可能是因为自身指令密度高、IPC 低,或者函数内部某条动态路径反复执行。
优化建议的入口:最后把结果组织成适合工程师继续行动的形式,例如按 score 排序、按调用路径导出 IPC 趋势、把重建后的调用栈写回 Chrome Trace,或者进一步看 Dynamic CFG edge。
因此,ALTPT 不是要替代 perf、Intel PT 或 Chrome
Trace,而是站在这些底层 trace 能力之上,补上“从大量 trace
事实到可操作性能线索”的中间层。它希望让一次性能分析从“我有一堆事件和符号”变成“我应该优先检查这些函数、这些路径、这些异常模式”。
从输入上看,ALTPT 主要服务两类数据: - Chrome Trace JSON:适合分析已经带有 begin/end 或 complete event 的时间线数据,回答“哪些函数或阶段在时间维度上可疑”。 - perf script / Intel PT decoded trace:适合从指令级执行流重建调用栈、估算 IPC,并进一步构建函数内 Dynamic CFG,回答“CPU 到底在怎么执行”。
基于这两类输入,ALTPT
提供三种分析模式:duration、cache 和
edge。它们分别对应从粗到细的三种观察尺度:函数耗时统计、调用栈级
IPC 分析、函数内动态路径分析。
入口方式:
python3 altpt.py <trace> [options]python3 cli.py <trace> [options]--pattern duration)输入:Chrome Tracing JSON(.json /
.json.gz),即 traceEvents 格式。
核心流程: 1.
core.load_trace_file() 加载 JSON,用
Decimal 把 ts(微秒浮点)和
dur 转换为整数纳秒,避免精度丢失。 2.
duration.reconstruct_duration_events() 将
ph=B/E 事件对重建为 ph=X(带
dur)事件;原本已是 ph=X 的直接保留。 3.
core.compute_children_counts()
用时间区间嵌套关系重建父子关系,为每个事件标注: -
has_child:是否有子函数 -
num_of_children:子函数调用次数 -
num_of_unique_children:不重复子函数种类数 -
loop_size =
num_of_children / num_of_unique_children(估算循环展开倍数)
1. 可选 --leaf / --leaf-parent
过滤:只保留叶子函数,或只保留”所有子函数均为叶子”的父函数。 2.
按函数名聚合统计:count、avg、max、min、total、max_avg(最大值/均值比)、sd_avg(标准差/均值比)、cachemiss(估算)。
3. compute_score() 对结果计算综合得分(log 归一化 +
线性归一化,权重:count=0.5, total=0.20, avg=0.20, max_avg=0.05,
sd_avg=0.05)。 4. 阈值过滤支持表达式如
count>=10,total>50,avg<200(运算符:>=, <=, >, <, =)。
关键指标:
| 指标 | 含义 |
|---|---|
| count | 函数调用次数 |
| avg / max / min / total | 执行时间统计 |
| max_avg | 最大偏差系数(检测偶发慢) |
| sd_avg | 标准差系数(检测抖动) |
| cachemiss | 慢执行超出均值的累积量 / 150(估算 cache miss 贡献) |
| avg_child_nb | 平均子调用数 |
| avg_loop_size | 估算循环倍数 |
| score | 综合得分(用于排序) |
--pattern cache)输入:perf script --itrace=i0ns --ns -Fcomm,tid,pid,time,cpu,event,ip,sym,addr,symoff,flags,callindent
输出的文本文件(.perf / .gz)。
目标:从指令级 decoded trace 重建调用栈,统计每个函数的指令数和时间,估算 IPC。
核心流程: 1.
core.iter_perf_trace_lines() 逐行读取(支持 gzip)。 2.
core.parse_perf_line() 用正则提取:comm,
pid/tid, timestamp,
payload(指令助记符 + 地址 + 符号)。 3.
core.split_mnemonic() 分离助记符(如 call,
ret,
jmp),core.classify_event() 分类为
call/return/jmp/jcc/syscall/none。 4.
core.extract_function_and_offset() 从 payload
里提取函数名和 +0xNNN 偏移。 5.
_handle_function_transition()
依据上一条指令的事件类型驱动调用栈变更,处理以下情况: -
call/syscall:push callee -
return:pop callee,跳回 caller;若 caller
不在栈中则 push(trampoline 场景) -
jcc:条件跳转跳到已有函数则 pop-to,否则 push -
jmp:尾调用 / trampoline 跳转 -
none:通过 offset == 0 判断是否为隐式函数入口 -
无法解析时清栈(self-heal,不 crash)
inst(inclusive),对栈顶累积
self_insttime_ns 和
self_time_ns输出指标:
| 指标 | 含义 |
|---|---|
| inst | 平均 inclusive 指令数(含 callee) |
| self_inst | 平均 self 指令数(仅本函数) |
| time / self_time | 平均 inclusive / self 执行时间 (ns) |
| ipc | inclusive IPC = inst / (time_ns × freq_GHz) |
| self_ipc | self IPC |
| count | 函数 push 次数(近似调用次数) |
| depth | 平均栈深 |
| trace_time_pct | inclusive 时间占总 trace 时长的百分比 |
可选输出: -
--chrometracing FILE:把重建的调用栈写成 Chrome Trace
JSON(由 ChromeTraceLogger 实现) -
--ipc-trending FILE:按调用路径写 IPC 趋势 CSV(由
IPCTrendingCollector 实现) -
--output FILE:把全部结果写成 CSV
--pattern edge)输入:同 cache 模式(perf script 指令级 trace)。
目标:构建函数内的 Dynamic
CFG,将连续在同一函数符号内执行的指令序列聚合为一条
edge,统计执行次数、时间和指令数。 Edge 定义:Edge
= (symbol, first_ip, last_ip),只保存首尾 IP
避免内存爆炸。同一序列(首尾 IP 相同)视为同一条 edge。
核心逻辑: - 每个线程维护一个
PendingEdge,在 符号变化 时 finalize
当前 edge 并开始新的。 - 同符号内的时间戳变化会累积到
accumulated_dt;指令计数持续累加。 - 每条 edge
最终记录:count、total_dt、avg_dt、avg_inst_count、kind(fall-through/branch/call/ret)。
可选:--memdump + --debug 开启
addr2line 解码(EdgeAddr2LineDecoder),将 next_ip
转换为源文件:行号,需要内存映射文件和带调试符号的 ELF。
close() 时导出标准 Chrome Trace
JSON(ph=B/E,displayTimeUnit=ns)。->
分隔的路径)、depth、inclusive_ipc、self_ipc、ipc_drop_from_parent、call_count。ipc_delta(ipc_drop_from_parent)=
本节点 inclusive IPC − 父节点 inclusive IPC,反映调用子函数带来的
IPC 下降。