Profiling.sampling – 统计性性能剖析器

Hacker News Top 工具

摘要

Python 3.15 引入了 profiling.sampling 模块,即 Tachyon,一种统计性性能剖析器,它会定期采样堆栈快照,开销极小,适用于开发和生产环境。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/05/12 16:00

# profiling.sampling — 统计性能分析器 **源代码:** `Lib/profiling/sampling/` (https://github.com/python/cpython/tree/main/Lib/profiling/sampling/) --- Tachyon 标志 (https://docs.python.org/3.15/_images/tachyon-logo.png) `profiling.sampling` 模块,名为 **Tachyon**,通过定期采样调用栈对 Python 程序进行统计性能分析。Tachyon 可以直接运行脚本,也可以附加到任何正在运行的 Python 进程,而无需修改代码或重启。由于采样是在目标进程外部进行的,开销几乎为零,因此 Tachyon 既适合开发环境也适合生产环境。 ## 什么是统计性能分析? (https://docs.python.org/3.15/library/profiling.sampling.html#what-is-statistical-profiling) 统计性能分析通过定期捕获调用栈的快照来构建程序行为的图像。与确定性性能分析器(即插装每个函数调用和返回)不同,Tachyon 以固定间隔读取调用栈,记录当前正在执行的代码。这种方法基于一个简单原则:消耗大量 CPU 时间的函数会在收集的样本中频繁出现。通过在一次性能分析会话中收集数千个样本,Tachyon 可以构建出对时间消耗的精确统计估计。收集的样本越多,估计就越精确。 下面的交互式可视化演示了采样性能分析的工作原理。点击 **播放** 观察一个 Python 程序的执行过程,并观察性能分析器如何定期捕获调用栈的快照。调整 **采样间隔** 观察采样频率对结果的影响。 ### 时间如何估计 (https://docs.python.org/3.15/library/profiling.sampling.html#how-time-is-estimated) Tachyon 输出中显示的时间值是 **从样本计数推导出的估计值**,而非直接测量值。Tachyon 统计每个函数在收集的样本中出现的次数,然后乘以采样间隔来估计时间。例如,在 10 kHz 采样率下,对 10 秒的性能分析,Tachyon 大约收集 100,000 个样本。如果一个函数出现在 5,000 个样本中(占总数的 5%),Tachyon 估计它消耗了 10 秒的 5%,即大约 500 毫秒。这是一个统计估计,而非精确测量。 这些估计的准确性取决于样本数量。在 100,000 个样本下,5% 的函数测量值误差大约为 ±0.5%。而如果只有 1,000 个样本,同样的 5% 测量值可能实际上代表 3% 到 7% 的真实时间。这就是为什么更长的性能分析持续时间和更短的采样间隔能产生更可靠的结果——它们能收集更多样本。对于大多数性能分析,默认设置已足够准确,可以识别瓶颈并指导优化工作。 由于采样是统计性的,不同运行之间的结果会略有差异。一次运行显示 12% 的函数可能在另一次运行中显示 11% 或 13%。这是正常且预期的。关注整体模式而非精确百分比,不必在意不同运行之间的小幅变化。 ### 何时使用其他方法 (https://docs.python.org/3.15/library/profiling.sampling.html#when-to-use-a-different-approach) 统计采样并非在所有情况下都理想。对于不到一秒内完成的非常短的脚本,性能分析器可能无法收集足够样本以获得可靠结果。请改用 `profiling.tracing` (https://docs.python.org/3.15/library/profiling.tracing.html#module-profiling.tracing),或者将脚本放在循环中运行以延长性能分析时间。 当需要精确的函数调用次数时,采样无法提供。采样通过快照估计频率,因此如果需要精确知道一个函数被调用了多少次,请使用 `profiling.tracing` (https://docs.python.org/3.15/library/profiling.tracing.html#module-profiling.tracing)。 当比较两个实现时,如果差异可能只有 1-2%,采样噪声会掩盖真实差异。请使用 `timeit` (https://docs.python.org/3.15/library/timeit.html#module-timeit) 进行微基准测试,或使用 `profiling.tracing` (https://docs.python.org/3.15/library/profiling.tracing.html#module-profiling.tracing) 进行精确测量。 与 `profiling.tracing` (https://docs.python.org/3.15/library/profiling.tracing.html#module-profiling.tracing) 的关键区别在于测量方式。跟踪性能分析器插装你的代码,记录每次函数调用和返回。这提供了精确的调用次数和精确的时间测量,但会给每个函数调用增加开销。而采样性能分析器则以固定间隔从外部观察程序,不修改其执行。可以这样理解两者的区别:跟踪就像有人跟着你,写下你走的每一步;而采样就像每隔一秒拍一张照片,从这些快照中推断你的路径。 这种外部观察模型使采样性能分析在生产环境中变得实用。被分析程序以全速运行,因为其内部没有运行插装代码,并且目标进程在采样期间从未被停止或暂停——Tachyon 直接从进程的内存中读取调用栈,而进程继续运行。你可以附加到正在运行的服务器上,收集数据,然后分离,应用程序甚至不会意识到自己被观察过。其代价是:生命周期极短的函数可能被错过,如果它们恰好发生在两次采样之间。 统计性能分析擅长回答“我的程序的时间花在哪里?”这个问题。它能揭示生产代码中的热点和瓶颈,而确定性性能分析的开销在这种情况下是不可接受的。如果需要精确的调用次数和完整的调用图,请改用 `profiling.tracing` (https://docs.python.org/3.15/library/profiling.tracing.html#module-profiling.tracing)。 ## 快速示例 (https://docs.python.org/3.15/library/profiling.sampling.html#quick-examples) 分析一个脚本并立即查看结果: ``` 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 ``` ## 命令 (https://docs.python.org/3.15/library/profiling.sampling.html#commands) Tachyon 通过多个子命令运行。`run` 和 `attach` 随时间收集样本;`dump` 捕获单个快照;`replay` 将二进制性能分析文件转换为其他格式。 ### `run` 命令 (https://docs.python.org/3.15/library/profiling.sampling.html#the-run-command) `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` 命令 (https://docs.python.org/3.15/library/profiling.sampling.html#the-attach-command) `attach` 命令通过进程 ID 连接到一个已经运行的 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 ``` 在大多数系统上,附加到另一个进程需要适当的权限。请参阅平台要求 (https://docs.python.org/3.15/library/profiling.sampling.html#profiling-permissions) 了解特定于平台的要求。 ### `dump` 命令 (https://docs.python.org/3.15/library/profiling.sampling.html#the-dump-command) `dump` 命令打印运行进程的 Python 栈的单个快照后退出,类似于 traceback: ``` python -m profiling.sampling dump 12345 ``` 与 `attach` 不同,`dump` 不运行采样循环:它只读取一次栈。这对于调查挂起或无响应进程很有用,或者用于回答“这个进程现在在做什么?”。输出类似 traceback(最近的调用最后列出),并注释每个线程的当前状态(主线程、持有 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` 需要权限才能读取目标进程的内存。请参阅平台要求 (https://docs.python.org/3.15/library/profiling.sampling.html#profiling-permissions)。 `dump` 命令支持以下选项: `-a`, `--all-threads` 转储目标进程中的每个线程。若无此标志,仅显示主线程。 `--native` 包含合成的 `<c-ext>` 帧,标记进入 C 扩展或其他非 Python 代码的转换。 `--no-gc` 隐藏标记活跃垃圾回收的合成 `<gc>` 帧。 `--opcodes` 用线程当前正在执行的字节码操作码(例如 `opcode=CALL_KW`)注释每个帧。对于指令级调查很有用,包括识别自适应解释器选择的特化。 `--async-aware` 跨 `await` 边界重建栈。`dump` 遍历任务图,并为每个任务输出一个部分,用 `<awaiter>` 标记分隔相互等待的协程。 `--async-mode {running,all}` 控制当启用 `--async-aware` 时包含哪些任务。`running` 仅显示每个线程当前执行的任务;`all`(`dump` 的默认值)还包括在 wait 上挂起的任务。`attach` 的此标志默认值为 `running`;`dump` 默认为 `all`,因为单个快照在显示完整任务图时最为有用。 `--blocking` 在读取栈时暂停目标中的每个线程,并在完成后恢复它们。保证完全一致的快照,代价是短暂停止目标。不使用此标志时,`dump` 在目标继续运行时读取内存,速度更快但偶尔可能产生撕裂的栈。 ### `replay` 命令 (https://docs.python.org/3.15/library/profiling.sampling.html#the-replay-command) `replay` 命令将二进制性能分析文件转换为其他输出格式: ``` python -m profiling.sampling replay profile.bin python -m profiling.sampling replay --flamegraph -o profile.html profile.bin ``` 此命令在你以二进制格式捕获性能分析数据,并希望稍后分析或将其转换为可视化格式时很有用。二进制性能分析文件可以多次回放为不同格式,而无需重新分析。 ``` # 将二进制转换为 pstats(默认,打印到 stdout) python -m profiling.sampling replay profile.bin # 将二进制转换为火焰图 python -m profiling.sampling replay --flamegraph -o output.html profile.bin # 将二进制转换为 gecko 格式,用于 Firefox Profiler python -m profiling.sampling replay --gecko -o profile.json profile.bin # 将二进制转换为热力图 python -m profiling.sampling replay --heatmap -o my_heatmap profile.bin ``` ### 在生产环境中进行性能分析 (https://docs.python.org/3.15/library/profiling.sampling.html#profiling-in-production) 采样性能分析器设计用于生产环境。它对目标进程没有可测量的开销,因为它是从外部读取内存,而非插装代码。目标应用程序继续全速运行,并且不知道正在被分析。 在分析生产系统时,请记住以下准则: 从较短的持续时间(10-30 秒)开始以快速获得结果,如果需要更高的统计准确性,再延长。默认情况下,性能分析一直运行到目标进程完成,这通常足以识别主要热点。 如果可能,在代表性负载下(而非峰值流量下)进行性能分析。在正常操作期间收集的性能分析结果比在异常峰值期间收集的结果更容易解释。 性能分析器本身会消耗操作机器上的一些 CPU(而非目标进程)。在同一台机器上,这通常可以忽略不计。当分析远程进程时,网络延迟不会影响目标。 生产环境的结果可能与开发环境不同,原因包括数据大小不同、并发负载或缓存效应。这是预期的,并且通常正是你想要捕获的。 ### 平台要求 (https://docs.python.org/3.15/library/profiling.sampling.html#platform-requirements) 性能分析器读取目标进程的内存以捕获栈回溯。这在大多数操作系统上需要提升的权限。 **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` 权限才能读取另一个进程的内存。 ### 版本兼容性 (https://docs.python.org/3.15/library/profiling.sampling.html#version-compatibility) 性能分析器和目标进程必须运行相同的 Python 次版本(例如,都是 Python 3.15)。不支持从 Python 3.14 附加到 Python 3.15 进程。 预发布 Python 版本有额外限制:如果性能分析器或目标运行的是预发布版本(alpha、beta 或 release candidate),则两者必须运行完全相同的版本。 在自由线程的 Python 构建中,性能分析器不能从自由线程构建附加到标准构建,反之亦然。 ## 采样配置 (https://docs.python.org/3.15/library/profiling.sampling.html#sampling-configuration) 在探索各种输出格式和可视化选项之前,了解如何配置采样过程本身很重要。性能分析器提供多个选项,用于控制样本收集的频率、性能分析运行的时间长度以及性能分析器如何与目标交

相似文章

Python 3.15:未登上头条的功能

Hacker News Top

Python 3.15 引入了较小但值得注意的功能,包括优雅的 TaskGroup 取消和装饰器的上下文管理器改进,以及诸如延迟导入和 tachyon 分析器之类的主要功能。