profiling --- Python 性能分析器

Added in version 3.15.

源代码: Lib/profiling/


性能分析简介

性能分析结果 <profile> 是一组统计数据,用于描述程序的各个部分执行的频率和耗时。这些统计数据有助于识别性能瓶颈并指导优化工作。Python 提供了两种从根本上不同的方法来收集这些信息:统计采样和确定性跟踪。

profiling 包将 Python 的内置性能分析工具组织在同一个命名空间下。它包含两个子模块,每个子模块分别实现一种不同的性能分析方法:

profiling.sampling

一种统计性能分析器,会定期对调用栈进行采样。可以直接运行脚本,也可以通过 PID 附加到正在运行的进程。它提供多种输出格式(火焰图、热图、Firefox Profiler)、GIL 分析、GC 跟踪以及多种性能分析模式(挂钟时间、CPU、GIL),并且几乎没有开销。

profiling.tracing

一种确定性性能分析器,会跟踪每一次函数调用、返回和异常事件。它提供精确的调用次数和准确的计时信息,能够捕获每一次调用,包括执行速度很快的函数。

备注

性能分析器模块的设计目标是为给定程序提供执行性能分析结果,而不是用于基准测试。进行基准测试时,请使用 timeit 模块,它能提供相当准确的计时测量。在比较 Python 代码和 C 代码时,这一区别尤其重要:确定性性能分析器会给 Python 代码引入开销,但不会给 C 层级函数引入开销,这可能导致比较结果出现偏差。

选择性能分析器

对于大多数性能分析任务,请使用统计性能分析器 (profiling.sampling)。它开销极小,既适用于开发环境也适用于生产环境,并提供丰富的可视化选项,包括火焰图、热图、GIL 分析等。

当你需要 精确的调用次数 且不能遗漏任何函数调用时,请使用确定性性能分析器 (profiling.tracing)。由于它会检测每一次函数调用和返回,因此即使是在采样间隔之间完成的极快函数也会被捕获。代价是开销更高。

下表总结了主要区别:

特性

统计采样 (profiling.sampling)

确定性 (profiling.tracing)

开销

几乎没有

中等

准确性

统计估计

精确调用次数

输出格式

pstats、火焰图、热图、gecko、折叠格式

pstats

性能分析模式

挂钟时间、CPU、GIL

挂钟时间

特殊帧

GC、原生帧(C 扩展)

N/A

附加到 PID

何时使用统计采样

对于大多数性能分析任务,建议使用统计性能分析器 (profiling.sampling)。它的使用方式与 profiling.tracing 相同:

python -m profiling.sampling run script.py

采样性能分析器的主要优势之一是它支持多种输出格式。除了传统的 pstats 表格之外,它还可以生成用于可视化调用层级的交互式火焰图、精确显示代码中时间消耗位置的行级源代码热图,以及用于基于时间线分析的 Firefox Profiler 输出。

该性能分析器还可以洞察确定性性能分析无法捕获的 Python 解释器行为。使用 --mode gil 可识别多线程代码中的 GIL 争用,使用 --mode cpu 可测量排除 I/O 等待后的实际 CPU 时间,或检查 <GC> 帧以了解垃圾回收开销。--native 选项会显示 C 扩展中花费的时间,有助于区分 Python 开销和库性能。

对于多线程应用,-a 选项会同时采样所有线程,以显示工作是如何分布的。对于生产环境调试,attach 命令可以通过 PID 连接到任何正在运行的 Python 进程,而无需重启或修改代码。

何时使用确定性跟踪

确定性性能分析器 (profiling.tracing) 会检测每一次函数调用和返回。此方法的开销高于采样,但可以保证完整覆盖程序执行过程。

选择确定性跟踪的主要原因是你需要精确的调用次数。统计性能分析会基于采样估计频率,可能会少计在两次采样之间完成的短生命周期函数。如果你需要验证某项优化是否确实减少了函数调用次数,或者想跟踪完整调用图以理解调用方与被调用方的关系,那么确定性跟踪就是合适的选择。

确定性跟踪也非常擅长捕获以微秒级执行的函数。这类函数在统计采样中可能不会足够频繁地出现,但确定性跟踪会记录每一次调用,而不受持续时间影响。

快速入门

本节提供开始进行性能分析所需的最少步骤。完整文档请参阅各性能分析器的专门页面。

统计性能分析

要分析脚本的性能,请使用 profiling.sampling 模块并搭配 run 命令:

python -m profiling.sampling run script.py
python -m profiling.sampling run -m mypackage.module

这会在性能分析器下运行脚本,并打印一份显示时间消耗位置的摘要。要生成交互式火焰图:

python -m profiling.sampling run --flamegraph script.py

要分析一个已经在运行的进程,请使用 attach 命令并指定进程 ID:

python -m profiling.sampling attach 1234

对于自定义设置,请指定采样间隔(以微秒为单位)和持续时间(以秒为单位):

python -m profiling.sampling run -i 50 -d 30 script.py

确定性性能分析

要从命令行分析脚本的性能:

python -m profiling.tracing script.py

要以编程方式分析一段代码的性能:

import profiling.tracing
profiling.tracing.run('my_function()')

这会在性能分析器下执行给定代码,并打印一份摘要,显示精确的函数调用次数和计时。

理解性能分析输出

两种性能分析器都会收集函数级统计数据,但呈现格式不同。采样性能分析器提供多种可视化形式(火焰图、热图、Firefox Profiler、pstats 表格),而确定性性能分析器会生成兼容 pstats 的输出。无论格式如何,底层概念都是相同的。

关键性能分析概念:

直接时间 (也称为 自身时间tottime)

执行函数自身代码所花费的时间,不包括它调用的其他函数所花费的时间。直接时间较高表示该函数包含开销较大的操作。

累计时间 (也称为 总时间cumtime)

在该函数及其调用的所有函数中花费的时间。这衡量的是调用一个函数的总成本,包括它的整个调用子树。

调用次数 (也称为 ncallssamples)

函数被调用(确定性)或被采样(统计性)的次数。在确定性性能分析中,此值是精确的。在统计性能分析中,它表示该函数出现在栈样本中的次数。

原始调用

不是由递归引起的调用。 当函数递归时,总调用次数包括递归调用,但原始调用次数只统计初始进入次数。 显示格式为 total/primitive (例如 3/1 表示总共调用三次,其中一次为原始调用)。

调用方/被调用方关系

哪些函数调用了给定函数(调用方),以及该函数调用了哪些函数(被调用方)。火焰图会将其可视化为嵌套矩形;pstats 可以通过 print_callers()print_callees() 方法显示这些关系。

旧版兼容性

为了保持向后兼容,cProfile 模块仍可作为 profiling.tracing 的别名使用。使用 import cProfile 的现有代码将在所有未来的 Python 版本中继续无需修改即可运行。

自 3.15 版本弃用: 纯 Python 的 profile 模块已被弃用,并将在 Python 3.17 中移除。 请改用 profiling.tracing (或其别名 cProfile)。 迁移指导请参阅 profile

参见

profiling.sampling

带有火焰图、热图和 GIL 分析功能的统计采样性能分析器。推荐大多数用户使用。

profiling.tracing

用于精确调用次数的确定性跟踪性能分析器。

pstats

性能分析数据的统计分析和格式化。

timeit

用于测量小段代码执行时间的模块。

子模块