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)。由于它会检测每一次函数调用和返回,因此即使是在采样间隔之间完成的极快函数也会被捕获。代价是开销更高。
下表总结了主要区别:
特性 |
统计采样 ( |
确定性 ( |
|---|---|---|
开销 |
几乎没有 |
中等 |
准确性 |
统计估计 |
精确调用次数 |
输出格式 |
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)
在该函数及其调用的所有函数中花费的时间。这衡量的是调用一个函数的总成本,包括它的整个调用子树。
- 调用次数 (也称为 ncalls 或 samples)
函数被调用(确定性)或被采样(统计性)的次数。在确定性性能分析中,此值是精确的。在统计性能分析中,它表示该函数出现在栈样本中的次数。
- 原始调用
不是由递归引起的调用。 当函数递归时,总调用次数包括递归调用,但原始调用次数只统计初始进入次数。 显示格式为
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用于测量小段代码执行时间的模块。
子模块