Python 性能诊断:时间、分配、存活与解释器边界
前置:对象与迭代协议。性能优化先写下相同输入/输出、规模、解释器与衡量指标,再定位成本。异步和生成器是控制流工具,不直接代表更快。
本轮复核仅静态阅读正文、源码与测试规格,未执行示例或测试;文中原有实测版本记录属于此前批次,本轮不复用为修改后的运行证明。命令与断言结果均为读者后续验证预期。
完整测量实验
文件 examples/python/pipeline-lab/performance.py 只用标准库;两种实现计算相同整数平方和,先断言相等再分别测时间与峰值。计时不同时开启tracemalloc,以免把跟踪开销混入正常时间。结果会随机器变化,程序不硬编码速度断言。
"""Compare identical totals; report time and traced peak, not universal claims."""
import argparse
import cProfile
import timeit
import tracemalloc
def materialized(n):
return sum([number * number for number in range(n)])
def streamed(n):
return sum(number * number for number in range(n))
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--profile', action='store_true')
args = parser.parse_args()
assert materialized(10000) == streamed(10000)
for function in (materialized, streamed):
elapsed = timeit.repeat(lambda: function(100000), number=5, repeat=3)
tracemalloc.start()
function(100000)
_, peak = tracemalloc.get_traced_memory()
tracemalloc.stop()
print(function.__name__, 'seconds=', elapsed, 'peak_bytes=', peak)
if args.profile:
cProfile.runctx('streamed(100000)', globals(), locals(), 'pipeline.prof')
if __name__ == '__main__':
main()PowerShell 在项目目录运行 python performance.py --profile;Bash用 python3。输出两项各三次时间和 peak_bytes;profile文件 pipeline.prof 写入当前目录,覆盖同名实验文件。然后 python -m pstats pipeline.prof,在提示符输入 sort cumulative、stats 10、quit;它显示累计调用时间与调用次数。只采样这一个函数调用,不能据此声称真实业务全部热点已覆盖。
如何解释结果
列表先保存所有平方结果,生成器边产生边累加,所以这里通常减少被跟踪的峰值对象内存;但两者都保留range迭代状态并执行Python整数计算。生成器的暂停/恢复也有成本,速度未必更快;先比较正确性,再报告三轮结果范围,而非只挑最快数字。timeit 的每次结果包含5次函数调用,若换算每次须除以5,不能把批时间直接当单次耗时。
tracemalloc 只跟踪其启动后受Python内存分配器管理的相关分配,不等于进程RSS、全部原生扩展内存或文件缓存。引用仍在字典/list/闭包/任务中就仍然存活;删除一个名称不表示没有其他引用。CPython引用计数通常及时释放无环对象,循环GC处理部分容器循环;不能把此实现细节当作所有Python实现的确定清理协议。文件、锁、事务应显式with,不等待GC。
避免将“函数被调用多次”直接等同于主要耗时:cProfile包含子调用的累计时间与函数自身时间不同。对CPU瓶颈考虑改算法/减少工作;对I/O瓶颈检查等待、批次和并发上限;对内存瓶颈查中间容器、长期引用与无界缓存。微基准只隔离局部,最后还要在真实数据和错误路径下确认收益。
从实验回到流水线
有界流水线已经限制输入队列和任务数,但保序结果仍存dict,占用O(n)输出内存,源也同步读取;它不是任意规模最优实现。若输出巨大,先决定是否需要事务式“全成功才输出”,再设计有界提交/重排协议。若只需总量,可在单一消费者中聚合并释放逐行结果,避免多个worker无锁修改共享总和。改变输出语义前先加测试。
工具调试优先顺序:确认 sys.executable/虚拟环境,运行现有unittest,缩小到可重复输入,再用profile与内存快照解释。pdb可在具体异常点检查局部变量;日志不应打印整个敏感输入。性能数字不替代异常、取消、空输入和资源释放的行为测试。
练习
① tracemalloc峰值下降能否宣称RSS一定下降?② 将n从10万改成100万时,哪种实现额外容器内存随n增长?③ del rows 后为什么还有内存?④ 给所有函数加async能提速吗?
参考解法
① 不能,分配器缓存、原生扩展与进程其他内存不在同一指标内。② materialized 的列表及保存结果随n增长,streamed不保留整组中间整数,但整数本身位数随n增长。③ 仍有其他引用或分配器暂未还页,也可能观测的是不同指标;先查引用路径与快照而非强行gc。④ 不能,无真正可等待I/O时只引入额外状态机/调度,CPU工作仍占事件循环。
来源
2026-10-17核对官方中文timeit、英文profile、tracemalloc、内存管理。只报告真实执行的工具,未运行的系统profiler和第三方框架性能不在本批证据中。
继续阅读
- 开发实践
- 排错
- 开发实践