← Back to Index

ALTPT (Automated Lightweight Trace Performance Tool)

Snipaste_2026-04-30_10-33-22.jpg

1. 为什么要设计 ALTPT

做性能优化时,我们经常会遇到一个尴尬的问题:工具能给出大量事实,但很少直接告诉我们“下一步应该看哪里”。

传统 profiling 工具通常擅长回答局部问题。例如,火焰图可以告诉我们哪些函数累计时间长,perf report 可以告诉我们哪些指令或符号采样多,Chrome Trace 可以展示一次请求中各个阶段的耗时。但是这些信息往往停留在不同视角里:时间线是一种视角,指令流是一种视角,调用栈又是另一种视角。真正做优化时,人需要在这些视角之间来回切换,手工判断一个函数到底是“调用次数太多”“单次太慢”“偶发抖动很大”“IPC 太低”,还是“热点集中在函数内部某几条动态路径上”。

ALTPT 的设计目标就是把这一步尽量自动化:它不是只把 trace 文件打印成表格,而是尝试从 trace 中提炼出更接近优化决策的问题分类。也就是说,ALTPT 关注的不是“trace 里有什么”,而是“trace 暗示了什么性能 antipattern”。

它试图把一次分析拆成三层:

  1. 慢的分类器:先判断慢来自哪里。是调用次数太多,平均耗时太高,尾部延迟太大,还是 CPU 执行效率偏低。

  2. 慢的解释器:再解释为什么慢。一个高耗时函数可能是因为子调用过多,也可能是因为自身指令密度高、IPC 低,或者函数内部某条动态路径反复执行。

  3. 优化建议的入口:最后把结果组织成适合工程师继续行动的形式,例如按 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 提供三种分析模式:durationcacheedge。它们分别对应从粗到细的三种观察尺度:函数耗时统计、调用栈级 IPC 分析、函数内动态路径分析。

入口方式:


2. 三种分析模式

2.1 Duration 模式(--pattern duration

输入:Chrome Tracing JSON(.json / .json.gz),即 traceEvents 格式。

核心流程: 1. core.load_trace_file() 加载 JSON,用 Decimalts(微秒浮点)和 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 综合得分(用于排序)

2.2 Cache 模式(--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)

  1. 每条指令:

输出指标

指标 含义
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


2.3 Edge 模式(--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。


3. 可选输出组件

ChromeTraceLogger(chrome_recorder.py)

IPCTrendingCollector(ipc_trending.py)