一件冲孔板的分析要跑十几秒。我们打开 cProfile 找热点,改完,几乎没快。
profiler 自己就是负载
cProfile 对调用极频繁的小函数收的税特别重——每次调用都要记一笔。于是那些 调用次数百万级的函数在报告里被顶到最前面,而它们的真实占比远没有那么高。我们 按它的指引改了几处,实测收益微乎其微:报告里 4 倍的差距,真跑起来根本不存在。
换成靶向秒表之后才看清楚:在怀疑的那几段代码前后直接掐表,跑真实零件, 对比总时长。土办法,但它测的是真实执行,没有观测者效应。
第一大热点是一句 list()
edges = list(NCollection_List) # 1290 msOCP(OCCT 的 Python 绑定)的迭代器每走一步都要跨一次 C++/Python 边界并做一次 类型分发。实测 1212 个两元素列表要 1290 ms。
换成直接问 Size / First / Last:
n = lst.Size() # 5 ms258 倍。
它藏在建邻接表的循环里,每条边执行一次,是整件分析的第一大热点——冲孔板上占 整体的 24%。而它在 profiler 的报告里毫不起眼,因为它只是一次 list() 调用。
修法上有一条要说清楚:流形几何里一条边恰好属于两张面,所以 0 / 1 / 2 个 元素的快路径覆盖了实际全部情形;元素数 ≥ 3(非流形)时退回原来的慢迭代。快 路径不是赌,是有边界的:
逐边对照新旧两种取法,结果完全一致。
一条留给自己的纪律
性能这件事上我们踩过的坑,几乎都不是"算法选错了",而是测错了地方:
- profiler 把小函数虚高,于是优化打在没有收益的地方;
- 全量跑的时候树是动的(中途改了码),子进程比对与行号全乱,得到一次假红—— 这个坑踩过三次,现在的规矩是跑全量期间工作树必须冻结;
- 机器被别的项目抢占时,性能预算门禁会被挤爆,红得毫无信息量。
所以现在的做法很朴素:靶向秒表 + 真实零件 + 安静的机器。三样缺一样,测出来 的数就不值得据以改代码。