Files
TransPyC/PROF/1.py
T
TermiNexus 1808e90fd3 feat(projectrans): 主进程 PROF 自动落盘 + 编译耗时统计 + 分析脚本升级
- Projectrans.py 入口包 cProfile,整次编译自动导出 main_prof_{pid}.prof 到 ./PROF;成功/失败均输出 [TIMING] 总耗时(stderr)并落盘 main_time.txt
- Config.py 新增 --asan(透传 TPC_ASAN=1)+ 每次编译自动清理 PROF 目录 *.prof
- PROF/1.py 升级为综合报告(tottime/cumtime top + callers 定位 + 模块聚合 + 上次编译耗时)
2026-08-19 00:28:18 +08:00

161 lines
6.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""PROF 综合分析脚本:合并 worker + 主进程 cProfile,输出更有分析价值的报告。
用法:
python PROF/1.py
输入(PROF/ 目录下,每次编译自动清理并重新生成):
worker_prof_*.prof —— worker 子进程每任务 cProfile(Phase2Translator._ProfileWorker)
main_prof_*.prof —— 主进程整次编译 cProfile(Projectrans.py 入口)
输出:
1.txt —— 综合报告(总览 + tottime top + cumtime top +
热点函数 callers 定位 + 模块聚合)
combined_all.raw.prof —— 全部合并原始 profile(snakeviz/gprof2dot 用)
combined_worker_cum.txt —— worker cumulative(全量)
combined_worker_tottime.txt —— worker tottime(全量)
"""
import pstats
from pathlib import Path
PROF_DIR = Path(__file__).resolve().parent
# 关键热点关键词:命中即在 1.txt 中输出其 callers 定位(按实际热点增减)
HOT_SPOTS = (
'_path_isfile', '_LookupStubFuncType', '_Init', '_TryLoadClassMembersFromPyi',
'_TryLoadStructFromStub', 'vast_c.parse', 'finalize', '_LoadSha1Map',
'_BuildPyiCacheFile', '_translate_file', 'Convert', 'GenerateCCode',
'_build_current_ModuleSha1Map', 'iter_child_nodes', 'read of',
'iter_fields', 'walk', '_io.open', '_pickle.loads',
)
def _ModuleKey(fname: str) -> str:
"""将文件路径归并到顶层模块(lib/xxx.py 或 lib/xxx/yyy.py 的 xxx)。"""
parts = fname.replace('\\', '/').split('/')
try:
i = parts.index('lib')
except ValueError:
return fname.rsplit('/', 1)[-1]
# lib 之后的第一段即模块归属;直接文件则取文件名
if i + 1 < len(parts):
return parts[i + 1]
return parts[-1]
def _AggregateByModule(stats: pstats.Stats) -> list[tuple[str, int, float, float]]:
"""按顶层模块聚合 tottime/cumtime(ncalls 取和)。返回按 tottime 降序。"""
agg: dict[str, list[float]] = {}
for key, val in stats.stats.items():
fname = key[0]
tt, ct = val[2], val[3]
m = _ModuleKey(fname)
if m not in agg:
agg[m] = [0, 0.0, 0.0] # ncalls, tottime, cumtime
agg[m][0] += val[0]
agg[m][1] += tt
agg[m][2] += ct
rows = [(m, *v) for m, v in agg.items()]
rows.sort(key=lambda r: r[2], reverse=True)
return rows
def _WriteCallers(stats: pstats.Stats, fp, keyword: str) -> None:
"""输出含 keyword 的所有函数的 callers(谁在调它,调用次数)。"""
hits = [k for k in stats.stats if keyword in k[2]]
if not hits:
fp.write(f" [无匹配 {keyword!r} 的函数]\n")
return
for key in sorted(hits, key=lambda k: stats.stats[k][2], reverse=True)[:5]:
cc, nc, tt, ct, callers = stats.stats[key]
fp.write(f" {key[2]}: tottime={tt:.3f}s cumtime={ct:.3f}s ncalls={nc}\n")
if not callers:
fp.write(" callers: (无)\n")
continue
for ckey, ncc in sorted(callers.items(), key=lambda x: x[1], reverse=True)[:8]:
fp.write(f" <- {ckey[2]} x{ncc}\n")
def main() -> None:
worker_files = sorted(PROF_DIR.glob('worker_prof_*.prof'))
main_files = sorted(PROF_DIR.glob('main_prof_*.prof'))
all_files = main_files + worker_files
if not all_files:
raise SystemExit(f'PROF 目录无 .prof 文件: {PROF_DIR}')
# 合并全部(主进程 + worker)
stats = pstats.Stats(str(all_files[0]))
for f in all_files[1:]:
stats.add(str(f))
stats.dump_stats(str(PROF_DIR / 'combined_all.raw.prof'))
# ---- worker 单独的全量 cum / tottime(兼容旧脚本输出) ----
if worker_files:
wstats = pstats.Stats(str(worker_files[0]))
for f in worker_files[1:]:
wstats.add(str(f))
for name, sort in (('combined_worker_cum.txt', 'cumulative'),
('combined_worker_tottime.txt', 'tottime')):
with open(PROF_DIR / name, 'w', encoding='utf-8') as fp:
wstats.stream = fp
wstats.sort_stats(sort)
wstats.print_stats()
# ---- 1.txt 综合报告 ----
report = PROF_DIR / '1.txt'
total_tt = sum(v[2] for v in stats.stats.values())
total_ncalls = sum(v[0] for v in stats.stats.values())
with open(report, 'w', encoding='utf-8') as fp:
fp.write('=' * 72 + '\n')
# 上次编译总耗时(Projectrans.py 入口记录,成功/失败均落盘)
time_txt = PROF_DIR / 'main_time.txt'
if time_txt.exists():
try:
_status, _secs = time_txt.read_text(encoding='utf-8').strip().split('\t')
fp.write(f'上次编译状态: {_status} 总耗时: {_secs}s\n')
except Exception:
pass
fp.write(f'PROF 综合分析报告 生成时间: {Path(__file__).stat().st_mtime}\n')
fp.write(f'主进程 prof: {len(main_files)} 个, worker prof: {len(worker_files)} 个\n')
fp.write(f'总函数调用 {total_ncalls} 次, 总 tottime {total_tt:.1f}s\n')
fp.write('=' * 72 + '\n\n')
fp.write('【一】Top 40 热点函数 (tottime = 真实 CPU 消耗, 优先优化)\n')
fp.write('-' * 72 + '\n')
tmp = pstats.Stats(str(all_files[0]))
for f in all_files[1:]:
tmp.add(str(f))
tmp.stream = fp
tmp.sort_stats('tottime')
tmp.print_stats(40)
fp.write('\n')
fp.write('【二】Top 20 调用链耗时 (cumtime = 含子调用, 看大流程)\n')
fp.write('-' * 72 + '\n')
tmp.sort_stats('cumulative')
tmp.print_stats(20)
fp.write('\n')
fp.write('【三】热点函数 callers 定位(找出"谁在调用"以定点优化)\n')
fp.write('-' * 72 + '\n')
for kw in HOT_SPOTS:
fp.write(f'\n== {kw} ==\n')
_WriteCallers(stats, fp, kw)
fp.write('\n')
fp.write('【四】模块聚合耗时(按顶层模块归类, tottime 降序)\n')
fp.write('-' * 72 + '\n')
fp.write(f"{'模块':<40}{'ncalls':>10}{'tottime(s)':>12}{'cumtime(s)':>12}\n")
for m, nc, tt, ct in _AggregateByModule(stats):
fp.write(f'{m:<40}{nc:>10}{tt:>12.2f}{ct:>12.2f}\n')
fp.write('\n')
print(f'报告已生成: {report}')
print(f'原始合并: {PROF_DIR / "combined_all.raw.prof"}')
if __name__ == '__main__':
main()