
线上 Python 服务变慢时最常见的窘境是你想看瓶颈在哪却不想为 profiling 重启进程也不想给业务代码埋点。传统cProfile虽然精确但属于插桩型profiler每次函数调用都要记录开销高到难以直接挂到生产环境py-spy、pyinstrument等第三方采样工具虽然好用却要多装一个依赖。Python 3.15当前 3.15.0rc12026-08-04 发布预计 2026-10 GA带来了一个真正的改变标准库新增profiling.sampling代号 Tachyon内置统计采样 profiler。它支持run、attach、dump、replay四个子命令最高可采样到约 1MHz能直接按 PID 附到运行中的进程不需要改代码、不需要重启、不需要 pip install。本文基于官方 3.15.0rc1 文档把它拆成「原理 → 安装 → 四种子命令实战 → 输出格式全家桶 → 迁移注意」五个层次。读完之后你会知道什么时候用 cProfile什么时候用 Tachyon以及怎么用一行命令把线上进程扒光。一、为什么需要 Tachyon采样 vs 插桩性能分析器通常分成两类插桩型Instrumentation代表是cProfile。它在每次函数调用/返回处插入记录钩子精确统计调用次数与耗时。代价是开销较高通常需要修改代码或重启进程才能开启。采样型Sampling代表是 Tachyon、py-spy。它通过定时器按固定频率抓取当前调用栈用统计方式估算各函数耗时占比。代价是精度略低但开销极小可以长期挂在生产环境也支持直接 attach 到运行进程。图1采样型与插桩型性能分析器的原理差异概念示意非运行截图两者不是替代关系而是互补关系插桩求精确采样求低开销与在线能力。Tachyon 的采样率默认 1kHz每秒 1000 次可以用-r 10khz / 100khz提高文档中上限约为 1MHz。它还提供wall、cpu、gil、exception四种采样模式其中cpu/gil/exception与--async-aware互斥。二、环境准备安装 Python 3.15.0rc1注意Python 3.15 目前处于 RCRelease Candidate阶段功能已冻结、ABI 稳定但正式发布前仍可能有微调请以最终版本的官方文档为准。最稳的方式是去 python.org 下载页面 拿对应平台的安装包。如果你已经在用uv也可以尝试精确版本# 安装 Python 3.15.0rc1 uv python install 3.15.0rc1 用 3.15 跑命令 uv run --python 3.15 python -m profiling.sampling --help安装完成后先确认模块存在python -m profiling.sampling --help正常的话会看到run、attach、dump、replay四个子命令。如果提示No module named profiling.sampling说明你当前调用的不是 3.15 解释器。三、Tachyon 的四个子命令与核心参数Tachyon 被设计成一个命令行模块官方文档没有暴露import profiling.sampling的编程式 API。所有能力都通过python -m profiling.sampling 子命令调用。这与cProfile的python -m cProfile用法一致。图2Tachyon 采样模式与输出格式一览概念示意非运行截图核心子命令子命令作用典型场景run运行并分析一个脚本或模块本地复现慢脚本attach按 PID 附到正在运行的进程线上服务排障dump打印一次性的进程栈快照快速看一眼进程在做什么replay把二进制 profile 转成其他格式留存采样文件后再分析核心选项-r rate/--sampling-rate rate采样率默认1khz可写10khz、100khz等。-d seconds/--duration seconds采样时长默认跑完整个程序或 attach 时手动停止。--mode mode采样模式wall默认、cpu、gil、exception。--async-aware对 asyncio 程序重建跨await的调用栈与cpu/gil/exception不兼容。输出格式--pstats默认文本表、--flamegraph自包含 HTML、--geckoFirefox Profiler JSON、--heatmap行级热力图 HTML、--collapsedspeedscope 折叠栈、--live终端实时 TUI、--binary二进制文件供replay用。四、实战一从零 profile 一个慢脚本先写一个没有外部依赖、只靠 CPU 燃烧的脚本# slow_task.py import math import time def heavy(n80_000): return sum(math.sin(i) for i in range(n)) def main(): for _ in range(5): heavy() time.sleep(0.05) if name main: main()直接用run子命令跑python -m profiling.sampling run slow_task.py默认输出是pstats格式类似下面这样这是官方文档给出的输出格式示例仅用于说明列含义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直接/累计采样数如234/892表示直接命中 234 次累计含子调用 892 次。sample%/cumul%直接/累计时间占比。tottime/cumtime基于采样数 × 采样间隔估算的时间。如果想只看前 30 行并按tottime排序python -m profiling.sampling run --sorttottime --limit30 slow_task.py如果嫌终端输出不够直观可以直接生成火焰图python -m profiling.sampling run --flamegraph -o slow_task.html slow_task.py打开slow_task.html就能看到调用栈的热点分布。五、实战二attach 到正在运行的进程生产排障这是 Tachyon 最有价值的场景。假设你有一个 FastAPI 服务正在跑# 1. 找到进程 PID ps aux | grep uvicorn | grep -v grep拿到 PID 后采样 30 秒并生成火焰图python -m profiling.sampling attach \ --duration 30 \ --flamegraph \ -o hot.html \ 12345把12345换成实际 PID。整个过程中服务不需要重启业务代码也不需要改动。30 秒后打开hot.html最宽的塔尖就是当前最热的函数。如果服务里有大量 asyncio 任务记得加--async-aware否则采样到的栈会断在await边界python -m profiling.sampling attach \ --duration 30 \ --async-aware \ --flamegraph \ -o hot_async.html \ 12345需要注意权限Linux 上通常要求 profiler 与目标进程属于同一用户且内核允许 ptraceWindows 上需要足够的进程访问权限。如果 attach 失败先检查权限而不是怀疑命令。图3用 Tachyon 对线上进程做性能排障的五步流程概念示意非运行截图六、实战三四种采样模式定位 GIL 争用默认--mode wall统计的是墙钟时间包含 I/O 等待和线程被挂起的时间。生产环境最常见的问题其实是GIL 争用也就是多个线程在抢全局解释器锁。Tachyon 提供了专门的--mode gilpython -m profiling.sampling attach \ --duration 30 \ --mode gil \ --heatmap \ -o gil_heat \ 12345gil模式会统计「线程花在等待/持有 GIL 上的时间」。如果某一行代码的红色样本特别密集说明那附近正在频繁触发 GIL 切换。典型原因包括重计算 多线程混用大量的小对象分配触发 GC频繁调用释放 GIL 的 C 扩展后又立即抢回。cpu模式只看纯 CPU 时间适合排除 I/O 干扰python -m profiling.sampling run --mode cpu slow_task.pyexception模式关注异常处理相关的耗时适合排查「某个路径在频繁抛异常并捕获」的场景python -m profiling.sampling run --mode exception buggy_task.py再次提醒cpu/gil/exception与--async-aware不兼容因为统计口径不同不能同时要求 asyncio 栈重建。七、输出格式全家桶火焰图、Firefox Profiler、行级热力图Tachyon 不是只吐一个文本表而是覆盖了一整套生态格式# 自包含 HTML 火焰图最常用 python -m profiling.sampling run --flamegraph -o hot.html app.py Firefox Profiler 可导入的 JSON python -m profiling.sampling run --gecko -o gecko.json app.py 行级热力图HTML 目录 python -m profiling.sampling run --heatmap -o heatdir app.py speedscope 折叠栈 python -m profiling.sampling run --collapsed -o collapsed.txt app.py 终端实时 TUI像 top 一样看 python -m profiling.sampling run --live app.py 二进制保存稍后 replay 成火焰图 python -m profiling.sampling run --binary -o profile.bin app.py python -m profiling.sampling replay --flamegraph -o hot.html profile.binreplay的价值在于你可以让 CI 或生产环境只保留一个profile.bin等需要排查时再用本地机器转成火焰图。二进制格式默认用 zstd 压缩也可以--compression none关闭。八、零改动的另一面PEP 686 UTF-8 默认与迁移风险Python 3.15 另一个「零改动」变化是PEP 686默认 UTF-8 编码。从 3.15 开始当你不写encoding时open()、TextIOWrapper、stdin/stdout 都会使用 UTF-8而不是平台 locale 编码。这对大多数开发者是好事但对维护老代码的团队来说是个潜在破坏点。# Python 3.14 及之前 with open(legacy.txt) as f: # Windows 可能是 cp1252Linux 可能是 UTF-8 content f.read() Python 3.15 with open(legacy.txt) as f: # 默认 UTF-8 content f.read()如果你的项目里有通过默认编码写入的非 UTF-8 文件比如cp1252、latin-1、gbk升级到 3.15 后读取会出错或产生乱码。修复方式很明确显式声明编码。# 明确读取旧编码文件 with open(legacy.txt, encodinglatin-1) as f: content f.read() 如果你确实需要「平台 locale 编码」无论是否开启 UTF-8 模式 import locale with open(local.txt, encodinglocale.getencoding()) as f: content f.read()如果你需要临时回退旧行为可以用环境变量或命令行开关# 关闭 UTF-8 模式 PYTHONUTF80 python app.py # 或 python -X utf80 app.py长期方案不是关 UTF-8而是把所有隐式编码改成显式。建议在 CI 里先跑PYTHONUTF81或直接在 3.15 上跑配合python -W error::EncodingWarning把每个没写encoding的地方揪出来。九、避坑清单3.15 还是 RC生产环境不要直接升级等 2026-10 正式 GA现在适合在 staging 或个人项目试跑。attach 权限Linux/macOS 上通常需要与目标进程同一用户必要时检查ptrace_scope。Windows 需要相应进程访问权限。cpu/gil/exception与--async-aware互斥文档明确说明写命令时不要同时加。采样是统计性的执行极短、出现次数极少的函数可能被漏采不要拿它做「精确调用次数」分析那是 cProfile 的活。采样率不是越高越好1kHz 已经能覆盖大多数场景开到 1MHz 会显著增加被采样进程的开销只在短时、深度排查时使用。不要长时间挂在生产环境即便是低开销采样也会对进程有一定扰动建议一次只采 10–60 秒。总结与延伸Python 3.15 把「采样性能分析器」收进了标准库最大的意义不是性能数据有多精确而是把线上排障的门槛降到了最低不再需要装第三方工具、不再需要改代码、不再需要重启服务。profiling.sampling与cProfile形成互补一个负责本地精确剖析一个负责线上低开销采样。同时PEP 686 把 UTF-8 变成默认编码虽然对多数项目是免费红利但老代码里的隐式编码依赖可能会浮出水面升级前建议用 3.15 跑一遍测试并显式化所有open()的编码参数。如果你想继续深入建议直接读官方文档profiling.sampling 官方文档PEP 686 – Make UTF-8 mode defaultWhats New in Python 3.15