profiling.tracing --- 确定型性能分析器

在 3.15 版本加入.

源代码: Lib/profiling/tracing/


profiling.tracing 模块提供 Python 程序的确定型性能分析。 它会监视每个函数调用、函数返回以及异常事件,为它们分别记录精确的耗时。 这种方式提供了程序执行的准确调用计数和完整可视化,使它成为适合开发和测试场景的理想工具。

备注

本模块也可作为 cProfile 访问以保持向下兼容性。 cProfile 名称将继续在未来所有 Python 版本中可用。 请使用适合你的代码的任一导入风格:

# 首选(新风格)
import profiling.tracing
profiling.tracing.run('my_function()')

# 仍然可用(向下兼容)
import cProfile
cProfile.run('my_function()')

什么是确定型性能分析?

确定型性能分析 会捕获程序运行期间的每个函数调用、函数返回以及异常事件。 性能分析器将计量这些事件所用时间的精确间隔,提供有关程序行为的准确统计信息。

与定期对调用栈进行采样来计算耗时的 统计型性能分析 不同,确定型性能分析会记录每个事件。 这意味着你将获得准确的调用计数而不是统计近似值。 这种方式的代价是记录每个事件会引入可能减慢程序执行速度的额外开销。

Python 的解释型性质使得确定型性能分析是可行的。 解释器已经针对函数调用与返回发送事件,因此性能分析器可以利用这一机制设置钩子而无需修改代码。 增加的开销相对于固有的解释消耗是有限的,使得确定型性能分析适用于大多数开发流程。

确定型性能分析有助于解答这样的问题:Deterministic profiling helps answer questions like:

  • 这个函数调用花费了多少时间?

  • 我的程序的完整调用图景是怎样的?

  • 某个特定函数会调用哪些函数?

  • 是否发生了意外的函数调用?

调用次数统计可识别错误(异常高的调用次数)以及可内联展开的优化机会(高调用次数)。内部时间统计可揭示需要优化的“热点循环”。累计时间统计有助于识别算法效率低下之处。该分析器对累计时间的处理方式支持直接比较递归实现与迭代实现。

命令行接口

profiling.tracing 模块可以作为脚本被唤起以分析另一个脚本或模块:

python -m profiling.tracing [-o output_file] [-s sort_order] (-m module | script.py)

这将在性能分析器中运行指定的脚本或模块并将结果打印到标准输出(或将其保存到文件)。

-o <output_file>

将性能分析结果写入文件而不是标准输出。 输出文件可由 pstats 模块读取供后续分析。

-s <sort_order>

按指定的键对输出进行排序。此选项可接受 pstats.Stats.sort_stats() 所识别的任何排序键,例如 cumulative、time、calls 或 name。仅当未指定 -o 时,此选项才适用。

-m <module>

分析模块而非脚本。模块通过标准的导入机制进行定位。

在 3.7 版本加入: 用于 cProfile 的 -m 选项。

在 3.8 版本加入: 用于 profile 的 -m 选项。

编程使用示例

要对性能分析进行更多控制,请直接使用此模块的函数和类。

基本性能分析

最简便的方法是使用 run() 函数:

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

该函数会对给定的代码字符串进行性能分析,并将摘要输出到标准输出。如需保存结果以便后续分析:

profiling.tracing.run('my_function()', 'output.prof')

使用 Profile 类

Profile 类提供了细粒度的控制:

import profiling.tracing
import pstats
from io import StringIO

pr = profiling.tracing.Profile()
pr.enable()
# ... 要分析的代码 ...
pr.disable()

# 打印结果
s = StringIO()
ps = pstats.Stats(pr, stream=s).sort_stats(pstats.SortKey.CUMULATIVE)
ps.print_stats()
print(s.getvalue())

Profile 类还可作为上下文管理器:

import profiling.tracing

with profiling.tracing.Profile() as pr:
    # ... 要分析的代码 ...

pr.print_stats()

模块参考

profiling.tracing.run(command, filename=None, sort=-1)

执行一条命令分析性能并打印或保存结果。

此函数会在 __main__ 模块的命名空间中使用 exec() 执行 command 字符串:

exec(command, __main__.__dict__, __main__.__dict__)

如果未提供 filename,该函数将创建一个 pstats.Stats 实例,并将摘要输出到标准输出。如果提供了 filename,则会将原始配置文件数据保存至该文件中,以便后续使用 pstats 进行分析。

sort 参数用于指定打印输出的排序顺序,可接受任何由 pstats.Stats.sort_stats() 识别的值。

profiling.tracing.runctx(command, globals, locals, filename=None, sort=-1)

使用显式命名空间对命令的执行进行性能分析。

类似于 run(),但使用指定的 globals 和 locals 映射来执行命令,而不是使用 __main__ 模块的 namespace:

exec(command, globals, locals)
class profiling.tracing.Profile(timer=None, timeunit=0.0, subcalls=True, builtins=True)

负责收集执行统计信息的性能分析对象。

可选参数 timer 用于指定自定义计时函数。若未提供该参数,性能分析器将使用与平台相适应的默认计时器。当提供自定义计时器时,它必须返回一个表示当前时间的单一数值。如果计时器返回整数,则使用 timeunit 指定一个时间单位的持续时间(例如,0.001 表示毫秒)。

subcalls 参数控制性能分析器是否跟踪函数之间的调用关系;builtins 参数则控制是否对内置函数进行性能分析。

在 3.8 版本发生变更: 添加了上下文管理器支持。

enable()

开始收集性能分析数据。

disable()

停止收集性能分析数据。

create_stats()

停止收集数据,并将结果作为当前概况在内部记录。

print_stats(sort=-1)

根据当前配置文件创建一个 pstats.Stats 对象,并将结果输出到标准输出。

sort 参数用于指定排序顺序。它接受单个键,或用于多级排序的键元组;多级排序时采用与 pstats.Stats.sort_stats() 相同的值。

在 3.13 版本加入: 支持排序键元组。

dump_stats(filename)

将当前配置文件数据写入 filename。该文件可由 pstats.Stats 读取,以便后续分析。

run(cmd)

通过 exec() 对命令字符串进行性能分析。

runctx(cmd, globals, locals)

通过 exec() 使用指定的命名空间对命令字符串进行性能分析。

runcall(func, /, *args, **kwargs)

对函数调用进行性能分析。返回 func 返回的任何值:

result = pr.runcall(my_function, arg1, arg2, keyword=value)

备注

性能分析要求被分析代码能够正常返回。如果在性能分析过程中解释器终止运行(例如通过 sys.exit()),则无法获得任何结果。

使用自定义计时器

Profile 类接受一个自定义计时函数,从而允许您测量执行的不同方面,例如实际运行时间或 CPU 时间。将计时函数传递给构造函数:

pr = profiling.tracing.Profile(my_timer_function)

计时器函数必须返回一个表示当前时间的数值。如果返回的是整数,还需指定 timeunit,以标明一个时间单位的持续时间:

# Timer returns time in milliseconds
pr = profiling.tracing.Profile(my_ms_timer, 0.001)

为实现最佳性能,计时器函数应尽可能快速。性能分析器会频繁调用该函数,因此计时器的开销会直接影响性能分析的开销。

time 模块提供若干函数,可用作自定义定时器:

局限性

确定性分析在时间精度方面存在固有局限性。

底层计时器的分辨率通常约为1毫秒。测量精度不可能超过这一分辨率。通过足够多的测量,计时误差往往会趋于平均化,但单次测量结果可能不够精确。

事件发生到性能分析器捕获时间戳之间也存在延迟。同样,在读取时间戳之后、用户代码恢复之前,也会产生延迟。频繁调用的函数会累积这种延迟,导致其表现可能比实际速度更慢。这种误差通常每次调用时小于一个时钟周期,但对于被多次调用的函数而言,可能会变得相当显著。

profiling.tracing 模块(及其别名 cProfile)以低开销的 C 语言扩展形式实现,因此与已弃用的纯 Python profile 模块相比,这些时序问题并不那么明显。

参见

profiling

Python 性能分析工具概览和性能分析器选择指南。

profiling.sampling

用于生产环境的统计抽样分析器。

pstats

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

profile

Deprecated pure Python profiler (includes calibration documentation).