profiling.sampling --- 统计型性能分析器

在 3.15 版本加入.

源代码: Lib/profiling/sampling/


Tachyon 标志

profiling.sampling 模块,名称为 Tachyon,它通过定期栈采样来提供 Python 程序的统计型性能分析。 Tachyon 可以直接运行脚本或者附加到任何正在运行的 Python 进程而无需代码修改或重启。 由于采样是在目标进程之外发生的,因此额外开销几乎为零,这使得 Tachyon 同时适用于开发和生产环境。

Tachyon follows earlier work on external sampling profilers such as Austin, py-spy, Pyflame, and rbspy. We thank their authors and the wider profiling community for their work and ideas.

什么是统计型性能分析?

统计型性能分析通过定期捕获调用栈的快照来构建程序行为的图景。 Tachyon 并不会像确定型性能分析那样检查每次函数调用并返回数据,而是以固定间隔读取调用栈来记录代码的当前运行情况。

这种方式依赖于一个简单的原则:显著消耗 CPU 时间的函数将会在收集的样本中频繁出现。 通过在一次性能分析会话中收集数以千计的样本,Tachyon 将构造出有关时间消耗的准确统计估算值。 收集的样本越多,估算的结果就越准确。

下面的交互式可视化演示了采样性能分析的工作原理。 点击 Play 来观看 Python 程序的执行过程,并观察性能分析器如何周期性地捕获调用堆栈的快照。 调整 sample interval 来查看采样频率如何影响结果。

时间是如何估算的

Tachyon 输出中显示的时间值是 根据样本计数得出的估计值,而非直接测量结果。Tachyon 会统计每个函数在收集的样本中出现的次数,然后乘以采样间隔来估算时间。

例如,在10秒的测量过程中,以10 kHz的采样率,Tachyon将采集约100,000个样本。 如果函数 在5,000个样本中出现(占总数的5%),Tachyon会估算它消耗了10秒时长中的5%,即约500毫秒。这是一种统计估算,而非精确测量。

这些估计值的准确性取决于样本数量。如果样本量为100,000个,则函数 显示的5%对应的测量误差 约为±0.5%。而如果样本量仅为1,000个,同样的5%测量值实际上可能代表真实时间的3%到7%之间的任意值。

这就是为什么更长的性能分析时长和更短的采样间隔能产生更可靠的结果——因为它们收集的样本更多。对于大多数性能 分析而言,默认 的设置已能提供足够的精度,足以识别瓶颈并指导优化工作。

由于采样属于统计过程,不同运行结果之间会存在细微差异。例如,某次运行中函数 的读数为12%,下次运行时可能显示为11%或13%。这是正常且预料之中的现象。请关注整体趋势,而非精确的百分比,无需在意不同运行结果之间的微小波动。

何时应采用不同的方法

统计抽样方法并非适用于所有情况。

对于在不到一秒内完成的非常短的脚本,性能分析器可能无法收集到足够的样本以获得可靠的结果。 请改用 profiling.tracing,或者在循环中运行该脚本,以延长性能分析时间。

当您需要精确的调用次数时,采样无法提供这些数据。 采样是通过采集快照来估计频率的,因此如果你需要精确了解函数被调用了多少次,请使用 profiling.tracing。

在比较两个实现 时,如果两者之间的差异可能仅为 1-2%,采样噪声可能会掩盖实际差异。 请使用 timeit 进行微基准测试,或使用 profiling.tracing 进行精确测量。

它与 profiling.tracing 的关键区别在于测量方式。 追踪式性能分析器会在代码中插入跟踪代码,记录每次函数调用以及函数返回 。 这虽然能提供精确的调用次数和时间数据,但会给每次对函数的调用增加开销。 相比之下,采样式性能分析器则是在不修改程序执行的情况下,以固定间隔从外部观察程序。 可以将两者的区别理解为:追踪式性能分析就像有人跟随你并记录你走的每一步,而采样式分析则像每秒拍摄一张照片,并根据这些快照推断出你的行进路线。

正是这种外部观察模型,使得采样分析在生产环境中得以实际应用。被分析的程序以全速运行,因为其内部没有运行任何插入的代码,且在采样过程中,目标处理 程序绝不会被停止或暂停——在程序继续运行的同时,Tachyon 直接从其内存中读取调用堆栈。 您可以连接到正在运行的服务器,收集数据,然后断开连接,而应用 甚至不会察觉到自己曾被观察过。其取舍在于,如果某些生命周期极短的函数 恰好在两次采样之间完成,则可能会被遗漏。

统计型性能分析在回答“我的程序在哪些地方耗费了时间?”这个问题方面表现尤为出色。 它能够揭示生产代码中的热点和瓶颈,而在这些情况下,确定性性能分析带来的开销是无法接受的。 若需获取精确的调用次数和完整的调用图表,请改用 profiling.tracing。

快速样例

对一个脚本进行性能分析并立即查看结果:

python -m profiling.sampling run script.py

附带参数对一个模块进行性能分析:

python -m profiling.sampling run -m mypackage.module arg1 arg2

生成交互式的火焰图:

python -m profiling.sampling run --flamegraph -o profile.html script.py

按 PID 附加到正在运行的进程:

python -m profiling.sampling attach 12345

打印一个运行中进程的栈的单独快照:

python -m profiling.sampling dump 12345

使用实时模式进行实时监控 (按 q 退出):

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

采用更高采样率的60秒剖面数据:

python -m profiling.sampling run -d 60 -r 20khz script.py

生成逐行热力图:

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

启用操作码级性能分析以查看哪个字节码指令正在执行:

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

命令

Tachyon 通过一些子命令来进行操作。 run 和 attach 将多次收集采样;dump 捕获单个快照;replay 将二进制分析数据转换为其他格式。

run 命令

run 命令 会启动脚本Python 或模块 ,并从头开始对其进行性能分析:

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

在对脚本进行性能分析时,分析器会将目标程序在子进程中启动,等待其初始化,然后开始采集样本。 -m 中的旗标表示目标程序应作为模块运行 (等同于 python -m)。 目标后面的参数会被传递给被分析的程序:

python -m profiling.sampling run script.py --config settings.yaml

attach 命令

attach 命令通过PID连接到已运行的Python进程:

python -m profiling.sampling attach 12345

此命令 在排查生产系统中的性能 问题时尤为有用。目标进程无需进行任何修改,也无需重启。分析器会附加到进程上,在指定的时间段内收集样本,然后脱离进程并生成输出结果。

python -m profiling.sampling attach --live 12345
python -m profiling.sampling attach --flamegraph -d 30 -o profile.html 12345

在大多数系统上,连接到另一个进程需要相应的权限。 有关各平台的具体要求请参阅 平台要求。

dump 命令

dump 命令 会打印运行中的 Python 进程堆栈的单个快照,然后退出,类似于回溯:

python -m profiling.sampling dump 12345

与 attach 不同,dump 不会运行采样循环 :它只读取一次栈。 这对于排查程序卡死或无响应的情况处理 ,或者回答“这个处理程序现在正在做什么?”非常有用。

输出结果与回溯 (最新调用在后)的结构一致,并为每个线程 标注其当前状态(main线程 、拥有GIL 、CPU时间、等待GIL时间 、有异常引发或空闲):

Stack dump for PID 12345, thread 140735 (main thread, has GIL, on CPU; most recent call last):
  File "server.py", line 28, in serve
    await handle_request(req)
  File "handler.py", line 91, in handle_request
    result = expensive_call(req)

当目标的源文件可读时,dump 会打印每个帧 对应的源代码行,并突出显示正在执行的表达式 。

与 attach 类似,dump 需要权限才能读取目标进程的内存。 参见 平台要求。

dump 命令支持以下选项:

-a, --all-threads

从目标进程中导出所有线程。如果没有这个旗标 ,则只会显示主线程 。

--native

包括合成的 <native> 帧用于标记向 C 扩展或其他非 Python 代码的过渡。

--no-gc

隐藏标记为进行活跃垃圾回收的合成 <GC> 帧。

--opcodes

为每个帧添加注释,注明当前正在执行的线程所使用的字节码 (例如 opcode=CALL_KW)。 这对于指令级别的分析非常有用,包括识别自适应解释器所选择的特化形式。

--async-aware

在 await 边界之间重建堆栈。dump 遍历任务图,为每个任务生成一个部分,并用 <task> 标记分隔出等待彼此完成协作的协程。

--async-mode {running,all}

用于控制启用 --async-aware 时包含哪些任务。running 仅显示每个线程当前正在执行的任务;all (dump 的默认值)还会包含因等待而暂停的任务。attach 对此标志的默认值为 running;dump 默认设为 all,因为当单个快照显示完整的任务图时,其用途最为显著。

--blocking

在读取目标线程的栈时暂停该目标中的每个线程,之后再恢复这些线程。这种方法以短暂停止目标线程为代价,可确保获得完全一致的快照。若不使用此机制,dump 会在目标线程持续运行的同时读取内存;这种方式速度更快,但偶尔可能导致栈不完整。

replay 命令

replay 命令可将二进制性能分析文件转换为其他输出格式:

python -m profiling.sampling replay profile.bin
python -m profiling.sampling replay --flamegraph -o profile.html profile.bin

当您已使用二进制格式捕获了性能分析数据,并希望稍后对其进行分析或将其转换为可视化形式时,此命令 非常有用。二进制性能分析数据可以多次重放并转换为不同格式,而无需重新进行性能分析。

# Convert binary to pstats (default, prints to stdout)
python -m profiling.sampling replay profile.bin

# Convert binary to flame graph
python -m profiling.sampling replay --flamegraph -o output.html profile.bin

# Convert binary to gecko format for Firefox Profiler
python -m profiling.sampling replay --gecko -o profile.json profile.bin

# Convert binary to heatmap
python -m profiling.sampling replay --heatmap -o my_heatmap profile.bin

生产环境中的性能分析

采样分析器专为生产环境而设计。由于它是通过外部读取内存而非对代码进行插桩,因此不会对目标进程产生可测量的额外开销。目标应用程序将继续以全速运行,且不会察觉自己正被进行性能分析。

在对生产系统进行剖析时,请牢记以下指导原则:

先从较短的持续时间(10–30 秒)开始,以快速获得结果;如果需要对统计精度有更高要求,再逐步延长持续时间。默认情况下,性能分析会持续运行至目标进程完成;通常这足以识别出主要热点。

如果可能的话,应在典型负载期间而非高峰流量期间采集性能概况。在正常运行期间采集的性能概况比在异常峰值期间采集的更易于解读。

分析器本身会在其运行的机器上占用一定的 CPU 资源(而非在目标进程中占用)。在同一台机器上,这种占用通常可以忽略不计。在对远程进程进行性能分析时,网络延迟不会影响目标进程。

由于数据规模、并发负载或缓存效应存在差异,生产环境的结果可能与开发环境有所不同。这种情况在所预料之内,而且往往正是您希望捕捉到的结果。

平台要求

分析器会读取目标进程的内存以捕获堆栈跟踪。在大多数操作系统上,这需要具备更高权限。

Linux

在 Linux 上,性能分析器会使用 ptrace 或 process_vm_readv 来读取目标进程的内存。 这通常需要以下条件之一:

  • 作为 root 运行

  • 具有 CAP_SYS_PTRACE 能力

  • 调整 Yama ptrace 作用范围:/proc/sys/kernel/yama/ptrace_scope

默认的 ptrace_scope 值为 1,会将 ptrace 限制在仅适用于父进程。若允许绑定到由同一用户拥有的任意进程,则将其设置为 0:

echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope

macOS

在 macOS 上,性能分析器使用 task_for_pid() 来访问目标进程。这要求满足以下条件之一:

  • 作为 root 运行

  • 具有 com.apple.security.cs.debugger 权限的性能分析器二进制文件

  • 系统完整性保护 (SIP) 已禁用(不推荐这样做)

Windows

在 Windows 上,性能分析器需要管理员权限,或 SeDebugPrivilege 权限,才能读取其他进程的内存。

版本兼容性

分析器和目标进程必须运行相同的 Python 次要版本(例如,两者均为 Python 3.15)。从 Python 3.14 连接到 Python 3.15 进程的行为不受支持。

对于预发布版 Python 版本还适用额外限制:如果 Profiler 或目标端正在运行预发布版本(alpha 版、beta 版或发布候选版),则两者必须运行完全相同的版本。

在自由线程化 Python 构建版本中,性能分析器无法从自由线程化构建版本连接到标准构建版本,反之亦然。

采样配置

在探讨各种输出格式和可视化选项之前,有必要先了解如何配置采样过程本身。分析器提供了多个选项,用于控制样本的采集频率、性能分析运行的时长、需要观察的线程,以及每个样本中捕获的附加上下文信息。

默认配置在大多数使用场景中都能良好运行:

选项

默认值

--sampling-rate / -r 的默认值

1 kHz

--duration / -d 的默认值

运行至完成

--all-threads / -a 的默认值

仅限主线程

--native 的默认值

无 <native> 帧(C 代码时间归因于调用者)

--no-gc 的默认值

垃圾回收启用时包含的 <GC> 帧

--mode 的默认值

Wall-clock mode (all samples recorded)

--realtime-stats 的默认值

已禁用

--subprocesses 的默认值

已禁用

--blocking 的默认值

Disabled (non-blocking sampling)

采样率与持续时间

两个最基本的参数是采样率和持续时间。二者共同决定了在一次分析过程中将采集多少个样本。

--sampling-rate 选项(-r)用于设置样本采集频率。默认值为 1 kHz(每秒采集 1,000 个样本):

python -m profiling.sampling run -r 20khz script.py

较高的采样率能够捕获更多样本,并提供更细粒度的数据,但代价是使分析器 CPU 使用率略微升高;较低的采样率则能减少分析器开销,但可能会遗漏短生命周期的函数。对于大多数应用程序而言,默认采样率能在准确性和开销之间取得良好平衡。

--duration 选项(-d)用于设置性能分析的持续时间(以秒为单位)。默认情况下,性能分析会一直持续,直到目标进程退出或被中断:

python -m profiling.sampling run -d 60 script.py

在附加到长期运行的进程时,或希望将性能分析限制在特定时间窗口内时,指定持续时间会很有用。在对脚本进行性能分析时,通常希望采用默认行为,即运行至完成。

线程选择

Python 程序通常会使用多线程,无论是通过 threading 模块显式实现,还是通过管理线程池的库隐式实现。

默认情况下,性能分析器仅对主线程进行采样。--all-threads 选项(-a)可启用对进程中所有线程的采样:

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

多线程性能分析可揭示工作如何在各线程间分配,并能识别出处于阻塞或资源饥饿状态的线程。各线程的采样数据会在输出中合并在一起,并支持按线程以某些格式进行筛选。当调查并发问题或工作被分布式到线程池时,此选项尤其有用。

阻塞模式

默认情况下,Tachyon 会在不中断目标进程的情况下读取其内存。这种非阻塞方式非常适合大多数性能分析场景,因为它几乎不会给目标应用程序带来任何开销:被分析的程序以全速运行,且不会察觉到自己正被监控。

然而,在非阻塞采样中,当应用程序包含大量生成器或协程,且这些生成器或协程在多个 yield 点之间快速切换时,或者当程序中的调用栈变化极快,导致函数在单次栈读取的起始与结束之间不断进入和退出时,偶尔会生成不完整或不一致的堆栈跟踪;这会导致重建的堆栈混合了来自不同执行状态的帧,或包含一些实际上从未真实存在的帧。

对于此类情况,--blocking 选项会在每次采样期间停止目标进程:

python -m profiling.sampling run --blocking script.py
python -m profiling.sampling attach --blocking 12345

当启用阻塞模式时,性能分析器会暂停目标进程,读取其栈,随后恢复该进程。这确保了每个捕获的栈都代表目标进程在某一时刻实际且一致的运行状态快照。其代价是目标进程的运行速度会变慢,因为它会反复被暂停。

警告

在阻塞模式下,请勿使用过高的采样率(即较低的 --interval 值)。进程的挂起和恢复需要一定时间;如果采样间隔过短,目标进程停止运行的时间将超过其运行时间。对于阻塞模式,建议采用 1000 微秒(1 毫秒)或更长的间隔。默认的 100 微秒间隔可能会导致目标应用程序出现明显的性能下降。

仅在观察到配置文件中存在不一致的堆栈时,才使用阻塞模式;这种情况尤其常见于包含大量生成器或协程的代码。对于大多数应用程序而言,默认的非阻塞模式能够提供准确的结果,且不会对目标进程产生任何影响。

特殊框架

分析器可以向捕获的堆栈中注入人工帧,从而为解释器在每次采样时正在执行的操作提供额外的上下文信息。这些合成帧有助于区分不同类型的执行行为;否则,这些行为将难以被察觉。

--native 选项会添加 <native> 帧,用于指示 Python 何时调用了 C 代码(扩展模块、内置函数或解释器本身):

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

这些框架有助于区分在 Python 代码中花费的时间与在原生库中花费的时间。若未启用此选项,原生代码的执行时间会显示为调用该代码的 Python 函数所花费的时间。在优化大量使用 C 扩展(如 NumPy 或数据库驱动程序)的代码时,这一功能非常有用。

默认情况下,当垃圾回收处于激活状态时,性能分析器会包含 <GC> 帧。--no-gc 选项会抑制这些帧:

python -m profiling.sampling run --no-gc script.py

GC 帧有助于识别垃圾回收占用大量时间的程序;这可能表明存在值得优化的内存分配模式。如果发现 <GC> 帧中占用了大量时间,可考虑调查对象分配率,或采用对象池技术。

基于操作码的性能分析

--opcodes 选项可启用指令级性能分析,用于捕获每个采样点正在执行的 Python 字节码指令:

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

该功能可让开发者了解 Python 字节码的执行情况,包括自适应特化优化。当像 LOAD_ATTR 这样的通用指令在运行时被特化为更高效的变体,例如 LOAD_ATTR_INSTANCE_VALUE 时,性能分析器会同时显示特化后的名称和基础指令。

OPCODE 信息以多种输出格式呈现:

  • 火焰图:将鼠标悬停在某个帧上时,会显示一个包含字节码指令分解的工具提示,其中会显示哪些 opcodes 在该函数中消耗了时间。

  • 热力图:按源代码行展开的可扩展字节码面板,可显示指令分解及各特化的百分比

  • 实时模式:操作码面板会显示所选函数的指令级统计信息,可通过键盘导航访问。

  • Gecko 格式: Opcode 转换会以间隔标记的形式显示在 Firefox Profiler 时间轴中

这种程度的细节尤其适用于以下情况:

  • 理解 Python 自适应特化对性能的影响

  • 识别可能受益于优化的热点字节码指令

  • 在指令级分析不同代码模式的有效性

  • 调试在字节码层面出现的性能问题

--opcodes 选项兼容 --live、--flamegraph、--heatmap 和 --gecko 格式。它需要额外的内存来存储操作码信息,可能会略微降低采样性能,但能以前所未有的方式揭示 Python 的执行模型。

实时统计

--realtime-stats 选项会在性能分析期间显示采样率统计信息:

python -m profiling.sampling run --realtime-stats script.py

这显示了实际达到的采样率;如果性能分析器无法跟上需求,该采样率可能会低于预期值。这些统计信息有助于验证性能分析是否正常运行,以及是否已收集到足够的样本。有关如何解读这些指标的详细信息,请参阅 采样效率。

子进程分析

--subprocesses 选项可启用对由目标进程所启动的子进程进行自动分析:

python -m profiling.sampling run --subprocesses script.py
python -m profiling.sampling attach --subprocesses 12345

启用后,性能分析器会监控目标进程是否创建子进程。当检测到新的 Python 子进程时,系统会自动启动一个独立的性能分析器实例,对该子进程进行性能分析。这对于使用 multiprocessing、subprocess、concurrent.futures 与 ProcessPoolExecutor 组合,或采用其他进程创建机制的应用程序非常有用。

worker_pool.py
from concurrent.futures import ProcessPoolExecutor
import math

def compute_factorial(n):
    total = 0
    for i in range(50):
        total += math.factorial(n)
    return total

if __name__ == "__main__":
    numbers = [5000 + i * 100 for i in range(50)]
    with ProcessPoolExecutor(max_workers=4) as executor:
        results = list(executor.map(compute_factorial, numbers))
    print(f"Computed {len(results)} factorials")
python -m profiling.sampling run --subprocesses --flamegraph worker_pool.py

这将为主进程和每个工作进程分别生成火焰图:flamegraph_<main_pid>.html、flamegraph_<worker1_pid>.html,依此类推。

每个子进程都会获得一个独立的输出文件。文件名由指定的输出路径(或默认路径)衍生而来,并在其后附加该子进程的进程 ID:

  • 如果您指定 -o profile.html,子进程将生成 profile_12345.html、profile_12346.html 等。

  • 在默认输出模式下,子进程会生成诸如 flamegraph_12345.html 之类的文件或诸如 heatmap_12345 之类的目录。

  • 对于 pstats 格式(默认输出至 stdout),子进程会生成诸如 profile_12345.pstats 之类的文件。

子进程性能分析器会从父进程继承大多数采样选项(采样率、持续时间、线程选择、原生帧、GC 帧、异步感知模式以及输出格式)。所有 Python 派生进程都会递归地进行性能分析,包括孙进程及更深层的派生进程。

子进程检测通过定期扫描目标进程的新子进程,并检测每个新进程是否为 Python 进程来实现;检测方式是通过探查进程内存中的 Python 运行时结构来完成。非 Python 子进程(例如 shell 命令或外部工具)会被忽略。

为防止在启动大量进程的程序中导致资源耗尽,同时运行的子进程分析器数量上限为 100 个。若达到此上限,则不会再对额外的子进程进行分析,同时会打印一条警告信息。

--subprocesses 选项与 --live 模式不兼容,因为实时模式采用交互式终端界面,无法同时支持多个 profiler 显示界面。

采样效率

采样效率指标可用于评估所采集数据的质量。这些指标会显示在分析工具的终端输出以及火焰图侧边栏中。

采样效率 是指采样尝试成功的百分比。每次采样尝试都会从内存中读取目标进程的调用栈。如果进程在读取时处于不一致状态,采样尝试可能会失败;例如在发生上下文切换期间,或解释器正在更新其内部结构时。效率较低可能表明采样器无法跟上所请求的采样率;这通常是由于系统负载过高,或采样间隔设置过于激进所致。

漏采样本 是指预期样本中未被采集到的百分比。根据配置的间隔和时长,分析器预期会采集一定数量的样本。如果分析器进度落后于计划,部分样本可能会被漏采,例如当系统处于高负载状态时。少量漏采样本属于正常现象,不会对分析结果的统计准确性产生显著影响。

这两项指标仅供参考。即使存在一些失败的尝试或遗漏的样本,只要采集了足够的样本,性能分析数据在统计上仍然有效。分析器会报告实际捕获的样本数,您可据此判断数据是否足以用于分析。

分析模式

采样分析器支持四种模式,用于控制记录哪些样本。模式决定了分析器测量哪些指标:累计经过时间、CPU 执行时间、持有全局解释器锁所花费的时间,或异常处理时间。

挂钟模式

Wall-clock 模式(--mode=wall)会捕获所有样本,而不受线程当前执行操作的影响。这是默认模式,能够完整呈现程序执行过程中时间的流逝情况:

python -m profiling.sampling run --mode=wall script.py

在挂钟模式(wall-clock mode)下,无论线程是正在主动执行 Python 代码、等待 I/O、被锁定阻塞,还是处于睡眠状态,系统都会记录样本。这使得实时性能分析非常适合用于了解程序的整体时间分布,包括等待所花费的时间。

如果您的程序在 I/O 操作、网络调用或睡眠中花费了相当长的时间,则 wall-clock 模式会将这些等待时间显示为调用函数的耗时。在优化端到端延迟时,这通常正是您所需要的。

CPU 模式

CPU 模式(--mode=cpu)仅在线程实际在某个 CPU 核心上执行时才会记录样本:

python -m profiling.sampling run --mode=cpu script.py

在线程处于休眠状态、被 I/O 阻塞或等待锁时采集的样本会被丢弃。由此生成的性能剖面图会显示 CPU 周期被消耗于何处,同时过滤掉空闲时间。

当您需要专注于计算热点,而不受 I/O 等待的干扰时,CPU 模式非常有用。如果您的程序在计算和网络调用之间交替执行,CPU 模式可以揭示哪些计算段最为耗时。

比较 Wall-clock 与 CPU 性能剖面

同时运行 wall-clock 模式和 CPU 模式的性能分析,可以揭示某个函数的运行时间究竟用于计算还是用于等待。

如果某个函数在两个配置中均显著出现,那么它就是真正的计算热点——正在积极占用 CPU 资源。优化工作应侧重于算法改进或编写更高效的代码。

如果某个函数在挂钟模式下占比较高,但在 CPU 模式下性能较低或几乎不运行,那么该函数处于 I/O 密集型状态或正在等待。此时,该函数大部分时间都在等待网络、磁盘、锁或睡眠操作;CPU 优化对此并无帮助;应考虑采用异步 I/O、连接池或缩短等待时间等措施。

import time

def do_sleep():
    time.sleep(2)

def do_compute():
    sum(i**2 for i in range(1000000))

if __name__ == "__main__":
    do_sleep()
    do_compute()
python -m profiling.sampling run --mode=wall script.py  # do_sleep ~98%, do_compute ~1%
python -m profiling.sampling run --mode=cpu script.py   # do_sleep absent, do_compute dominates

GIL 模式

GIL 模式(--mode=gil)仅在线程持有 Python 全局解释器锁时记录样本:

python -m profiling.sampling run --mode=gil script.py

GIL 仅在执行 Python 字节码期间保持激活状态。当 Python 调用 C 扩展、执行 I/O 操作或执行原生代码时,GIL 通常会释放。这意味着 GIL 模式能够专门测量 Python 代码运行的耗时,从而排除原生库中的耗时。

在多线程程序中,GIL 模式会揭示哪些代码正在阻止其他线程运行 Python 字节码。由于同一时间只能有一个线程持有 GIL,因此在 GIL 模式分析报告中出现频率较高的函数,实际上正在独占解释器资源。

GIL 模式有助于回答诸如“哪些函数正在独占 GIL?”以及“为什么我的其他线程处于饥饿状态?”等问题。在单线程程序中,它也有助于区分 Python 的执行时间与用于 C 扩展或 I/O 所花费的时间。

import hashlib

def hash_work():
    # C extension - releases GIL during computation
    for _ in range(200):
        hashlib.sha256(b"data" * 250000).hexdigest()

def python_work():
    # Pure Python - holds GIL during computation
    for _ in range(3):
        sum(i**2 for i in range(1000000))

if __name__ == "__main__":
    hash_work()
    python_work()
python -m profiling.sampling run --mode=cpu script.py  # hash_work ~42%, python_work ~38%
python -m profiling.sampling run --mode=gil script.py  # hash_work ~5%, python_work ~60%

异常模式

异常模式(--mode=exception)仅在某个线程发生有效异常时才会记录样本:

python -m profiling.sampling run --mode=exception script.py

样本会在两种情况下被记录:一是当异常沿调用堆栈向上传播时(在 raise 之后、被捕获之前);二是当代码在 except 块内部执行时,此时线程状态中仍包含异常信息。

以下示例说明了哪些代码区域会被捕获:

def example():
    try:
        raise ValueError("error")    # Captured: exception being raised
    except ValueError:
        process_error()              # Captured: inside except block
    finally:
        cleanup()                    # NOT captured: exception already handled

def example_propagating():
    try:
        try:
            raise ValueError("error")
        finally:
            cleanup()                # Captured: exception propagating through
    except ValueError:
        pass

def example_no_exception():
    try:
        do_work()
    finally:
        cleanup()                    # NOT captured: no exception involved

请注意,只有当异常正在这些 finally 块中主动传播时,这些 finally 块才会被捕获。一旦 except 块完成执行,Python 会在运行任何后续的 finally 块之前清除异常信息。同样,在正常执行期间(未引发任何异常时)运行的 finally 块也不会被捕获,因为此时不存在异常状态。

这种模式有助于了解您的程序在何处花费时间处理错误。对于使用异常进行流控制(例如迭代器中的 StopIteration)或需要处理多种错误情况的应用程序(例如处理连接失败的网络服务器)而言,异常处理可能会成为代码中一个显著的开销来源。

异常模式有助于回答诸如“处理异常所花费的时间是多少?”以及“哪些异常处理程序的性能开销最大?”这类问题。即使这些异常得到了妥善处理,该模式也能揭示出在捕获并处理大量异常的代码中隐藏的性能开销。例如,如果某个解析库在内部使用异常来指示格式错误,那么即使调用代码从未看到这些异常,该模式仍会捕获在这些处理程序中花费的时间。

输出格式

该分析器可生成多种格式的输出,每种格式适用于不同的分析工作流。通过命令行标志选择格式,输出内容则根据格式不同,写入 stdout、文件或目录。

pstats 格式

pstats 格式(--pstats)会生成一种与确定性分析器所生成结果类似的文本表格。这是默认的输出格式:

python -m profiling.sampling run script.py
python -m profiling.sampling run --pstats script.py
Tachyon pstats 终端输出

pstats 格式以彩色编码表格的形式展示分析结果,其中显示函数热点、样本计数和时序估计值。

默认情况下,输出会显示在 stdout 上:

Profile Stats (Mode: wall):
     nsamples  sample%    tottime (ms)  cumul%   cumtime (ms)  filename:lineno(function)
       234/892    11.7%       234.00     44.6%       892.00    server.py:145(handle_request)
       156/156     7.8%       156.00      7.8%       156.00    <built-in>:0(socket.recv)
        98/421     4.9%        98.00     21.1%       421.00    parser.py:67(parse_message)

各列显示采样次数及估计时间:

  • nsamples:以 direct/cumulative 的形式显示(例如 10/50)。直接样本是指函数处于堆栈顶部并正在主动执行时的样本;累积样本是指函数出现在堆栈的任意位置时的样本,包括等待被调用的函数时。如果某个函数显示为 10/50,则说明该函数有 10 个样本处于直接执行状态,且总计有 50 个样本处于调用堆栈中。

  • sample% 和 cumul%:分别表示直接计数和累积计数的样本占总样本数的百分比。

  • tottime 和 cumtime:根据样本数量和性能分析持续时间估算的实际运行时间。时间单位会根据数值大小自动选择:较大值使用秒,中等值使用毫秒,较小值则使用微秒。

输出内容包括一个说明各列含义的图例,以及一份重点介绍以下内容的有趣函数摘要:

  • 热点函数:直接样本与累计样本之比较高的函数(比值接近 1.0)。这类函数将大部分时间用于执行自身代码,而非等待被调用函数。高比值表明 CPU 时间真正消耗于这些函数。

  • 间接调用:累积样本与直接样本之间存在较大差异的函数。这类函数属于编排函数,会将工作委托给其他函数。它们频繁出现在调用栈中,但很少位于栈顶。

  • 调用级放大:指累积样本量远超过直接样本量的函数(累积/直接倍数较高)。这类函数通常具有嵌套特性,会出现在多个调用链的深层位置。

使用 --no-summary 同时抑制图例部分和摘要部分。

将 pstats 输出保存为二进制文件,而非输出到 stdout:

python -m profiling.sampling run -o profile.pstats script.py

pstats 格式支持多种用于控制显示方式的选项。--sort 选项用于确定用于对结果进行排序的列:

python -m profiling.sampling run --sort=tottime script.py
python -m profiling.sampling run --sort=cumtime script.py
python -m profiling.sampling run --sort=nsamples script.py

--limit 选项将输出限制为前 N 条记录:

python -m profiling.sampling run --limit=30 script.py

--no-summary 选项会抑制统计表之前的标题摘要。

折叠堆栈格式

折叠堆栈格式(--collapsed)会为每个唯一的调用堆栈生成一行,并记录该堆栈被采样的次数:

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

输出结果如下:

main;process_data;parse_json;decode_utf8 42
main;process_data;parse_json 156
main;handle_request;send_response 89

每行包含以分号分隔的函数名,这些函数名按从底到顶的顺序表示调用栈;随后是一个空格,以及样本数量。此格式旨在与外部火焰图工具保持兼容,尤其是 Brendan Gregg 的 flamegraph.pl 脚本。

从折叠堆栈生成火焰图:

python -m profiling.sampling run --collapsed script.py > stacks.txt
flamegraph.pl stacks.txt > profile.svg

生成的 SVG 可以在任何网页浏览器中查看,并提供交互式可视化功能,您可以点击以放大特定调用路径。

火焰图格式

Flame graph 格式(--flamegraph)会生成一个独立的 HTML 文件,其中包含交互式 Flame graph 可视化界面:

python -m profiling.sampling run --flamegraph script.py
python -m profiling.sampling run --flamegraph -o profile.html script.py
Tachyon 交互式火焰图

火焰图可视化将调用栈显示为嵌套矩形,其宽度与耗时成正比;侧边栏则显示运行时统计信息、GIL 指标以及热点函数。

Try the interactive example!

If no output file is specified, the profiler generates a filename based on the process ID (for example, flamegraph_12345.html).

生成的 HTML 文件无需任何外部依赖,可直接在网页浏览器中打开。可视化界面将调用栈以嵌套矩形形式呈现,矩形的宽度与所花费的时间成正比。将鼠标悬停在矩形上可查看该函数的详细信息,包括源代码上下文;点击矩形则可将调用树放大至该部分。

火焰图界面包括:

  • 侧边栏显示性能分析摘要、线程统计信息、采样效率指标(参见 采样效率)以及主要热点函数

  • 支持函数名称匹配和 file.py:42 行模式的搜索功能

  • 通过下拉菜单按线程筛选

  • Dark/light theme toggle (preference saved across sessions)

  • SVG 导出以保存当前视图

线程统计部分显示运行时行为指标:

  • GIL Held:线程持有全局解释器锁(正在运行 Python 代码)的样本百分比

  • GIL 释放率:未有任何线程持有 GIL 的样本百分比

  • 等待 GIL:线程正在等待获取 GIL 的样本所占百分比

  • GC:垃圾回收期间样本的百分比

这些统计数据有助于识别 GIL 争用情况,并了解时间是如何在 Python 执行、原生代码和等待之间分配的。

火焰图在识别深层调用栈和理解时间消耗的层级结构方面尤为有效。顶部的宽矩形表示那些直接或通过其被调用函数而消耗大量时间的函数。

差异火焰图

差异火焰图通过对比两次性能分析运行,突出显示性能发生变化的位置。这有助于识别因代码变更而导致的性能倒退,并验证所实现的优化是否达到了预期效果:

# Capture baseline profile
python -m profiling.sampling run --binary -o baseline.bin script.py

# After modifying code, generate differential flamegraph
python -m profiling.sampling run --diff-flamegraph baseline.bin -o diff.html script.py

该可视化会绘制当前的性能分析数据,其中帧宽表示当前的时间消耗;随后通过颜色标示出每个函数相对于基线发生了怎样的变化。

颜色编码:

  • 红色:耗时较长的函数(回归分析)。浅色表示适度增加,深色则表示严重回归。

  • 蓝色:用于耗时更短的函数(性能提升)。浅色调表示适度降低,深色调表示显著提速。

  • 灰色:几乎无变化或无变化。

  • 紫色:基线中不存在的新函数。

帧颜色表示**直接时间**的变化(即函数处于堆栈顶部、正在主动执行时的时间),而非包含被调用函数在内的累计时间。将鼠标悬停在帧上时,会显示对比详情,包括基准时间、当前时间及百分比变化。

某些调用路径可能会在配置文件之间完全消失。这类路径被称为**省略堆栈**;当优化操作移除了代码路径,或某些分支停止执行时,就会发生这种情况。如果存在省略堆栈,则会显示一个“省略”切换控件,允许您在主差异视图与仅显示已移除路径的省略视图之间进行切换(这些路径以紫色显示)。

Gecko 格式

Gecko 格式(--gecko)生成的 JSON 输出与 Firefox Profiler 兼容:

python -m profiling.sampling run --gecko script.py
python -m profiling.sampling run --gecko -o profile.json script.py

Firefox Profiler 是一款功能先进的基于 Web 的工具,最初是为对 Firefox 本身进行性能分析而开发的。它提供的功能不仅限于基础火焰图,还包括时间线视图、调用树探索以及标记可视化。有关详细使用说明,请参阅 Firefox Profiler documentation。

要使用输出结果,请在浏览器中打开 Firefox Profiler,并加载 JSON 文件。该分析器完全在客户端运行,因此您的分析数据永远不会离开您的机器。

Gecko 格式会自动收集有关 GIL 状态和 CPU 活动的额外元数据,从而支持针对 Python 线程模型而设计的特定分析功能。性能分析器会生成间隔标记,这些标记会在 Firefox Profiler 时间轴中以彩色条带的形式显示:

  • GIL 标记:用于显示线程何时持有或释放全局解释器锁

  • CPU 标记:显示线程是在 CPU 上执行还是处于空闲状态

  • 代码类型标记:用于区分 Python 代码与原生(C 扩展)代码

  • GC 标记:表示垃圾回收活动

因此,--mode 选项在 Gecko 格式下不可用;所有相关数据均会自动捕获。

Firefox Profiler 调用树视图

调用树视图会显示完整的调用层级结构,以及样本数量和百分比。侧边栏则显示所选函数的详细统计信息,包括运行时间和样本分布。

Firefox Profiler 火焰图视图

火焰图可视化将调用栈显示为嵌套矩形;函数名称则显示在调用层级结构中。

带有操作码的 Firefox Profiler 标记图

标记图会显示间隔标记,包括 CPU 状态、GIL 状态和操作码。当 --opcodes 启用时,诸如 BINARY_OP_ADD_FLOAT、CALL_PY_EXACT_ARGS 和 CALL_LIST_APPEND 等字节码指令会以标记形式显示,反映其随时间推移的执行情况。

热图格式

热图格式(--heatmap)可生成交互式 HTML 可视化界面,以源行级别显示样本计数:

python -m profiling.sampling run --heatmap script.py
python -m profiling.sampling run --heatmap -o my_heatmap script.py
Tachyon 热力图可视化

热图将样本计数直接叠加在您的源代码上。线条采用颜色编码,从冷色(样本数量少)到暖色(样本数量多)表示不同水平。导航按钮(▲▼)可让您在调用者和被调用者之间跳转。

与其他生成单个文件的格式不同,热图输出会创建一个目录,其中包含每个经过分析的源文件的 HTML 文件。若未指定输出路径,则目录名称将设为 heatmap_PID。

热力图可视化通过颜色渐变显示您的源代码,颜色深浅表示每条代码行采集的样本数量。热线(样本较多)呈现暖色调,而冷线(样本较少或无样本)则呈现冷色调。这种视图有助于准确定位导致耗时问题的具体代码行。

热力图界面提供多种交互式功能:

  • 着色模式:可在“自时”(直接执行)与“总时”(累加计时,包含被调用函数内的时间)之间切换

  • 冷代码过滤:显示所有行,或仅显示包含样本的行

  • 调用图导航:每行显示导航按钮(▲ 表示调用者,▼ 表示被调用者),通过这些按钮可以追溯代码中的执行路径。当某一行调用了多个函数,或从某一行被多个函数调用时,会显示一个菜单,列出所有选项及其样本数量。

  • 滚动小地图:一种垂直概览视图,显示整个文件的热分布情况

  • 层级索引:按类型(stdlib、site-packages、project)对文件进行组织,并统计每个文件夹中的汇总样本数量

  • 深色/浅色主题:可通过切换实现,偏好设置将在不同会话间保存

  • 行链接:点击行号即可生成可共享的 URL

当启用 --opcodes 进行指令码级性能分析时,每条热点代码段都可以进一步展开,以显示哪些字节码指令消耗了时间:

带扩展字节码面板的热图

展开热点行可查看已执行的字节码指令,包括专用变体。该面板显示每条指令的样本数量以及该行的总体特化百分比。

Try the interactive example!

当你知道哪个文件存在性能问题,但需要找出具体是哪几行时,热力图尤其有用。许多开发者更倾向于使用这种格式,因为它能直接对应到源代码,便于阅读和定位。对于较小的脚本和有针对性的分析而言,热力图能提供直观的视图,准确显示时间消耗的具体位置,而无需对层次化可视化结果进行解读。

二进制格式

二进制格式(--binary)可生成紧凑的二进制文件,从而高效存储分析数据:

python -m profiling.sampling run --binary -o profile.bin script.py
python -m profiling.sampling attach --binary -o profile.bin 12345

--compression 选项用于控制数据压缩:

  • auto (默认):若支持 zstd 压缩则启用压缩;否则不压缩

  • zstd: Force zstd compression (requires compression.zstd support)

  • none:禁用压缩

python -m profiling.sampling run --binary --compression=zstd -o profile.bin script.py

若要分析二进制配置文件,请使用 replay command 将其转换为其他格式,例如火焰图或 pstats 输出格式。

录制与回放工作流

二进制格式结合回放命令,可实现一种记录与回放工作流,从而将数据捕获与分析分离开来。在性能分析过程中,您无需生成可视化结果,而是将原始数据捕获到紧凑的二进制文件中,随后再将其转换为不同格式。

这种方法具有三大主要优势:

  • 采样运行速度更快,因为构建用于可视化的数据结构的工作会被推迟到回放阶段才执行。

  • 单次二进制捕获结果无需重新进行 profiling 即可转换为多种输出格式:pstats 用于快速概览,flame graph 用于可视化探索,heatmap 则用于行级细节分析。

  • 二进制文件体积小巧,便于与同事共享;对方可以将这些文件转换为自己偏好的格式。

典型工作流程:

# Capture profile in production or during tests
python -m profiling.sampling attach --binary -o profile.bin 12345

# Later, analyze with different formats
python -m profiling.sampling replay profile.bin
python -m profiling.sampling replay --flamegraph -o profile.html profile.bin
python -m profiling.sampling replay --heatmap -o heatmap profile.bin

实时模式

Live 模式(--live)提供基于终端的实时分析数据视图,类似于用于系统进程的 top 命令:

python -m profiling.sampling run --live script.py
python -m profiling.sampling attach --live 12345
Tachyon 实时模式:显示所有线程

实时模式会显示实时分析统计信息,呈现多线程应用程序中多个线程的合并数据。

随着新样本的到达,显示内容会持续更新,呈现当前最热门的函数。此模式需要 curses 模块;该模块可在类 Unix 系统上使用,但 Windows 系统上则无法使用。终端的宽度必须至少为 60 列,高度必须至少为 12 行;更大的终端将显示更多列。

标题栏显示最热门的 3 个函数、采样效率指标以及线程状态统计信息(GIL 持有百分比、CPU 使用率、GC 时间)。主表显示函数统计信息,当前排序列由箭头(▼)标示。

当 --opcodes 启用时,主表格下方会显示一个额外的操作码面板,其中呈现当前所选函数的指令级统计信息。该面板会显示哪些字节码指令执行频率最高,包括专用变体及其基础操作码。

带有操作码面板的 Tachyon 实时模式

启用 --opcodes 的实时模式会显示一个操作码面板,其中包含所选函数的字节码指令分解视图。

键盘命令

在实时模式下,键盘命令用于控制显示内容:

q

退出分析器,返回 shell。

s / S

按排序顺序前后循环切换(样本数量、百分比、总时长、累计百分比、累计时长)。

p

暂停或恢复显示更新。显示暂停期间,采样仍会在后台继续;因此,您可以冻结视图以查看结果,而无需中断数据收集。

r

重置所有统计数据并重新开始。在完成分析后,此功能将被禁用,以防止意外导致数据丢失。

/

进入筛选模式,即可按名称搜索函数。该筛选器会对文件名和函数名进行不区分大小写的子字符串匹配。输入一个模式并按 Enter 键应用筛选,或按 Escape 键取消筛选。不支持通配符模式和正则表达式。

c

清除当前筛选条件,再次显示所有函数。

t

可在查看所有线程合并统计与按线程统计之间切换。在按线程统计模式下,会显示线程计数器(例如 1/4),用于显示您在当前可用线程中的位置。

← → 或 ↑ ↓

在按线程查看时,可在各线程之间进行导航。导航会从最后一个线程循环返回到第一个线程,反之亦然。

+ / -

可增大或减小显示刷新率。范围从 0.05 秒(20 Hz,响应非常迅速)到 1.0 秒(1 Hz,系统开销较低)。刷新率越高,CPU 占用率越大。默认值为 0.1 秒(10 Hz)。

x

切换趋势指示器,用于显示各函数随时间推移是变得更热还是更冷。启用后,与上一次更新相比,上升的指标显示为绿色,下降的指标显示为红色。

h 或 ?

显示包含所有可用命令的帮助界面。

j / k (或 Up / Down)

在 opcode 面板中浏览 opcode 条目(当 --opcodes 启用时)。这些按键可滚动查看当前所选函数的指令级统计信息。

当分析完成时(持续时间到期或目标进程退出),界面会显示“分析完成”横幅,并冻结最终结果。在按下 q 退出之前,您仍可继续浏览、排序和筛选结果。

Live 模式与输出格式选项(--collapsed、--flamegraph 等)不兼容,因为它采用交互式终端界面,而非生成文件输出。

异步感知性能分析

对于使用 asyncio 的程序,性能分析器提供了一种异步感知模式(--async-aware),该模式会根据任务结构而非原始 Python 帧来重建调用栈:

python -m profiling.sampling run --async-aware async_script.py

对异步代码进行标准剖析可能会令人困惑,因为实际的调用栈往往显示的是事件循环的内部结构,而非协程的逻辑执行流程。异步感知模式通过跟踪当前正在运行的任务,并呈现反映 await 链的调用栈,从而解决了这一问题。

import asyncio

async def fetch(url):
    await asyncio.sleep(0.1)
    return url

async def main():
    for _ in range(50):
        await asyncio.gather(fetch("a"), fetch("b"), fetch("c"))

if __name__ == "__main__":
    asyncio.run(main())
python -m profiling.sampling run --async-aware --flamegraph -o out.html script.py

备注

异步感知式性能分析要求目标进程已加载 asyncio 模块。如果在脚本导入 asyncio 之前对其执行性能分析,异步感知模式将无法捕获任务信息。

异步模式

--async-mode 选项用于控制哪些任务会显示在配置文件中:

python -m profiling.sampling run --async-aware --async-mode=running async_script.py
python -m profiling.sampling run --async-aware --async-mode=all async_script.py

使用 --async-mode=running (默认设置)时,仅会对当前在 CPU 上正在执行的任务进行性能分析。这可以显示您的程序正在积极消耗时间的具体位置,也是性能分析的典型选择。

使用 --async-mode=all 时,处于暂停状态的任务(正在等待 I/O、锁或其他任务)也会被纳入其中。这种模式有助于了解程序正在等待什么,但会生成更冗长的性能剖面,因为每个暂停任务都会出现在每个采样点中。

任务标记与堆栈重建

在支持异步处理的性能分析报告中,你会看到标记 asyncio 任务边界的 <task> 帧。这些是性能分析器插入的合成帧,用于展示任务结构;在这些帧中,任务名称会以函数名称的形式显示。

当某项任务正在等待另一项任务时,性能分析器会通过追踪 await 关系来重建逻辑调用链。只有“叶节点”任务(即没有在等待其他任务的任务)才会生成自身的栈条目;而被其他任务等待的任务,则会作为其等待者栈的一部分出现。

如果某个任务存在多个 awaiter(即任务图中呈现菱形结构),性能分析器会确定性地选择一个父任务,并将父任务数量标注在任务标记上,例如 MyTask (2 parents)。这表明存在其他执行路径,但这些路径并未显示在当前堆栈中。

选项限制

Async-aware 模式采用不同的栈重建机制,因此与以下模式不兼容:--native、--no-gc、--all-threads,以及 --mode=cpu 或 --mode=gil。

命令行接口

完整的命令行界面,供参考。

全局选项

run

运行并分析 Python 脚本或模块。

attach

按 PID 附加到运行中的进程并对其进行性能分析。

dump

打印运行中进程的 Python 堆栈的一次性快照。

replay

将二进制配置文件转换为另一种输出格式。

转储选项

以下选项适用于 dump 子命令:

-a, --all-threads

将目标进程中的所有线程全部释放,而不仅仅是释放主线程。

--native

为非 Python 代码包含 <native> 帧。

--no-gc

排除 <GC> 帧,以进行主动垃圾回收。

--opcodes

在可用时显示字节码操作码名称。

--async-aware

为 asyncio 应用程序在 await 边界处重建堆栈。

--async-mode <mode>

异步堆栈模式:running (仅运行中的任务)或 all (包括等待中的所有任务)。对于 dump,默认设置为 all。需要 --async-aware。

--blocking

在读取堆栈期间,暂停目标进程中的所有线程。

采样选项

-r <rate>, --sampling-rate <rate>

采样率(例如:10000、10khz、10k)。默认值:1khz。

-d <seconds>, --duration <seconds>

分析时长(单位:秒)。默认值:运行至完成。

-a, --all-threads

对所有线程进行采样,而不仅仅是主线程。

--realtime-stats

在分析过程中显示采样统计信息。

--native

为非 Python 代码包含 <native> 帧。

--no-gc

排除 <GC> 帧以进行垃圾回收。

--async-aware

为 asyncio 程序启用异步感知型性能分析。

--opcodes

收集字节码操作码信息,用于指令级性能分析。显示正在执行的字节码指令,包括特化指令。仅兼容 --live、--flamegraph、--heatmap 和 --gecko 格式。

--subprocesses

同时分析子进程。每个子进程都会获得一个独立的 Profiler 实例和输出文件。与 --live 不兼容。

--blocking

在每次采样期间暂停目标进程。这样可以确保堆栈跟踪结果保持一致,但会以降低目标进程运行速度为代价。建议采用较长的间隔时间(1000 µs 或更长),以最大限度地减少影响。详情请参阅 阻塞模式。

模式选项

--mode <mode>

采样模式:wall (默认)、cpu、gil 或 exception。cpu、gil 和 exception 模式与 --async-aware 不兼容。

--async-mode <mode>

异步分析模式:running (默认值)或 all。需要 --async-aware。

输出选项

--pstats

生成 pstats 统计信息。这是默认行为。当输出到 stdout 时,结果将以文本表格形式呈现;若使用 -o,则生成二进制 pstats 文件。

--collapsed

为外部火焰图工具生成折叠堆栈格式。

--flamegraph

生成独立的 HTML 火焰图。

--diff-flamegraph <baseline.bin>

生成与基准二进制配置文件相比的差异性火焰图。

--gecko

为 Firefox Profiler 生成 Gecko JSON 格式。

--heatmap

生成包含行级样本计数的 HTML 热力图。

--binary

生成高性能二进制格式,以便后续通过 replay 命令进行转换。

--compression <type>

二进制格式的压缩选项:auto (若可用请使用 zstd,默认为此选项)、zstd 或 none。

-o <path>, --output <path>

输出文件或目录路径。默认行为因格式而异:--pstats 将文本表格输出到 stdout;而 -o 则写入二进制 pstats 文件。其他格式会生成一个名为 <format>_<PID>.<ext> 的文件(例如 flamegraph_12345.html)。--heatmap 则创建一个名为 heatmap_<PID> 的目录。

--browser

生成后,会自动在默认网页浏览器中打开 HTML 输出文件(--flamegraph 和 --heatmap)。使用 --subprocesses 进行性能分析时,仅主进程会打开浏览器;子进程的输出内容绝不会自动打开。

pstats 显示选项

这些选项仅适用于 pstats 格式的输出。

--sort <key>

排序顺序:nsamples、tottime、cumtime、sample-pct、cumul-pct、nsamples-cumul 或 name。默认值:nsamples。

-l <count>, --limit <count>

可显示的最大条目数。默认值:15。

--no-summary

从输出中省略“图例”和“有趣函数摘要”部分。

运行命令选项

-m, --module

将目标视作模块名而非脚本路径。

--live

启动交互式终端界面而不是执行性能分析批处理。

参见

profiling

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

profiling.tracing

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

pstats

性能分析数据的统计分析。

Firefox 性能分析器

接受 Gecko 格式输出的基于 Web 的性能分析器。 用法详情参见相关 文档。

FlameGraph

用于从展开的栈格式生成火焰图的工具。