返回资讯中心
外部精选
软件工程
#开发者实时流

The State of Python Profiling in 2026

$ cat the-state-of-python-profiling-in-2026.md

Hacker News 最新lumpa2026/11/5 · 今天30 分钟阅读

以下正文同步自 Hacker News 最新,版权归原站所有,已转换为易读排版。

$ cat the-state-of-python-profiling-in-2026.md

Fri 30 October 2026 26 min read programming/ #python #profiling #optimisation

CPython now ships its own sampling profiler, free-threading is going mainstream, and coding agents have started reading profiles. In this post I look at where Python profiling stands today, compare Tachyon, py-spy, Scalene and Austin, and go through what's new in Austin 4.1.

Python profiling has changed more in the last two years than in the decade before. CPython 3.14 gave external tools an official way to look inside a running interpreter, and made asyncio task graphs inspectable from outside the process. CPython 3.15 ships a sampling profiler in the standard library. Free-threaded builds have gone from experiment to something people actually deploy. And a growing share of the people reading profiles aren't people at all, but coding agents.

This post looks at where things stand: what the main sampling profilers can do today, where each one fits, and what changed in the latest release of Austin.

As many of you know, I've been working on Austin for the best part of eight years now, so I have a view from the inside on how this landscape came to be, and, naturally, a soft spot for one of the tools in it. I've tried to be fair to the others: what I say about them comes from their own documentation and release notes as of October 2026. If I've got something wrong, let me know and I'll fix it.

Most profilers make two basic choices, and these choices explain most of the differences between tools.

Tracing or sampling. A tracing (deterministic) profiler like cProfile records every function call and return. You get exact call counts, but the overhead changes the program's timing, sometimes a lot. A sampling profiler looks at the call stack at regular intervals and builds a statistical picture from those snapshots. You don't get call counts, but the program runs at close to normal speed. I went into the differences between the two, and how they work under the bonnet, in an earlier post.

In-process or out-of-process. An in-process profiler runs inside your program, usually by starting it for you or being imported. An out-of-process profiler is a separate program that reads the target's memory from outside. Out-of-process tools can attach to something that's already running, like a production server that started misbehaving an hour ago, without restarting it or changing its code.

All four tools in this post are sampling profilers. Tachyon, py-spy and Austin work from outside the process. Scalene works from inside.

There are many more Python profilers than I can cover here, so I've picked the ones I think are the most widely known and used, and that have a track record on accuracy and overhead. That track record isn't just my opinion. The most thorough comparison of Python profilers I know of, by the Scalene team at UMass Amherst (Berger, Stern and Altmayer Pizzorno, 2022), measured a dozen of them on the longest-running benchmarks of the pyperformance suite. Its conclusion was that sampling profilers are "both more efficient and often more accurate" than tracing ones.

The two go together. A tracing profiler runs its own code on every function call and return, inside your program, so it slows the program down, and not evenly: code that makes many small calls is slowed down far more than code that makes few, which skews the picture of where time is spent. The study found one tracing profiler reporting that a function took 80% of the run time when it actually took 25%. A sampling profiler stays out of the way of your code, so the timings it reports are much closer to how the program behaves when nobody is watching. In the study, Austin, py-spy and Scalene were among the profilers with the lowest overhead in CPU mode, adding between 0% and 2% to the run time, compared with 73% for cProfile and more than 200% for some others.

Tracing profilers still have their place: they count every call exactly, which sampling can't do. If you need to know that a function was called 1,204 times, use cProfile. If you need to know where the time goes, sample.

Tachyon is too new to have been part of that study, but as the profiler that now ships with Python, it's the one many people will reach for first, so it belongs here.

One notable absence is pyflame, Uber's profiler from 2016. It was one of the first to sample Python stacks from outside the process, and it deserves credit for showing that the approach works. But it was Linux-only, its last changes date from 2019, before Python 3.8 was released, and its repository is now archived and marked as no longer maintained. I mention a few other good tools at the end of the post.

With a sampling profiler, the time attributed to a function is an estimate: the fraction of samples it appears in, multiplied by the duration of the run. Like any statistical estimate, its error shrinks as the number of samples grows, roughly in proportion to 1/√n. Sampling 10 times faster gives you about three times less noise from the same run, so short runs give stable results and short-lived functions are less likely to be missed entirely.

So the maximum rate a profiler can sustain, without disturbing the target, directly limits how accurate it can be.

That's why raw sampling speed has been a priority for Austin from the start, and it's the reason it's written in C and does nothing but sample: every microsecond saved per sample is another sample per second. Each release has pushed this further. Austin 4.0 more than doubled the maximum sampling rate of 3.x, and 4.1 pushes it higher again (more on that below), with a default of 10,000 samples per second.

Tachyon's authors clearly think the same way: the profiler is named after the hypothetical particle that travels faster than light. Its sampling core is written in C too, and while its default rate is a more conservative 1,000 samples per second, it's designed to be raised well beyond that. When a profiler that's been around since 2018 and the one that has just joined the standard library both put speed first, it's a good sign that sampling rate is finally getting the attention it deserves.

Profiling Python from outside the process used to be close to a hack. CPython made no promises about the layout of its internal structures, so tools like Austin and py-spy had to copy large parts of the interpreter's private headers for every Python version, and work out where the interesting fields were from compiler- and platform-specific details. Every new release could break them, and often did.

Those tools showed that the approach was worth it. Being able to attach to a running process with no instrumentation and almost no overhead turned out to be winning proposition in production, and evidently it made a strong case for CPython to support out-of-process tools properly, rather than just tolerate them.

Some of that work came directly from Austin. In 2021, I made three changes to CPython, each to solve a problem I'd run into while working on Austin:

Tachyon reads both co_qualname and native_thread_id today. In 2023 I moved the fields that out-of-process tools rely on to the top of the interpreter's structures (gh-105271). That change was one of the references Pablo Galindo cited when he proposed the debug offsets table (gh-106597): a structure at the start of the runtime state that tells external tools where to find the data they need, without copying any headers. I later added more fields to it (gh-121311). The debug offsets table is the foundation that PEP 768's remote debugging interface is built on, and that Tachyon uses to read the interpreter's state.

So the profiler that now ships with CPython is, in a sense, the result of years of external tools proving that out-of-process sampling works. CPython made it a supported use case, and the core developers have now built a sampling profiler into the interpreter itself. I find that very satisfying, and it's good news for every external tool too, Austin included.

The biggest news of the year is that CPython 3.15 includes a sampling profiler, profiling.sampling, also known as Tachyon (it was proposed alongside reorganising cProfile into profiling.tracing, in PEP 799). It builds on the remote debugging support added in 3.14 by PEP 768.

python -m profiling.sampling run --flamegraph script.py python -m profiling.sampling attach 12345 python -m profiling.sampling run --live script.py

Tachyon covers a lot of ground for a first release:

The obvious advantage is that it's already there: nothing to install, and it's maintained alongside the interpreter, so it stays in step with internal changes.

The main limit is version coverage. Tachyon only profiles Python 3.15 and later, and the profiler and the target must run the same minor version (and the same build type: free-threaded or standard). If your services run on 3.12, Tachyon doesn't help you yet. Its --native option adds placeholder <native> frames to show where Python calls into native code, but doesn't unwind the native stack itself.

If you've used Austin, much of Tachyon will feel familiar. The basic approach of reading the interpreter's memory from outside the process is shared with py-spy, and goes back to pyflame in 2016. But beyond that, many of the features Tachyon has added since its first commit in August 2025 are ones Austin has had for years:

Some of these exist in py-spy too, and it goes the other way as well: Tachyon's asyncio support landed before Austin's. That's how open source should work, with good ideas spreading between projects. What's missing is the credit. PEP 799 doesn't mention Austin, py-spy or any of the external profilers whose approach Tachyon adopts. I think that's a missed opportunity to recognise the years of community work that came before the standard library caught up, and that made it possible.

py-spy, written in Rust, made out-of-process sampling mainstream for Python, and it's what many people mean when they say "Python sampling profiler".

py-spy record -o profile.svg --pid 12345 py-spy top --pid 12345 py-spy dump --pid 12345

Its record, top and dump commands are a model of a simple interface. It writes SVG flame graphs, Speedscope and Chrome trace files, can follow subprocesses, filter to threads holding the GIL, and include or exclude idle threads. With --native it also unwinds native C, C++ and Rust frames on Linux (x86_64 and, since recently, aarch64) and Windows.

py-spy 0.4.2, released in April 2026, added Python 3.14 support. At the time of writing, free-threaded builds aren't supported yet (there's an open issue tracking it), and there's no task-aware asyncio mode: coroutines appear as ordinary frames on whichever thread runs the event loop.

Scalene takes a different approach. It runs your program for you and profiles from the inside, which lets it measure things external tools can't easily see.

scalene run script.py

Its strengths are breadth and line-level detail:

Two of these numbers come with caveats. Scalene doesn't observe native code directly: it uses a timer signal, and treats any delay in handling it as time spent in native code, during which Python can't handle signals. But CPython only handles signals on the main thread, so in a program with several busy threads the delay can simply come from waiting for its turn on the GIL. In 2022, Pablo Galindo showed a multi-threaded, pure-Python program that Scalene reported as spending most of its time in native code (scalene#428). Scalene's author replied that worker threads are profiled in a different way, but the issue is still open, and the current code still estimates the main thread's native time from the signal delay. So treat the Python/native split with some care in multi-threaded programs.

For GPU time on NVIDIA, Scalene needs the GPU's per-process accounting mode, which has to be enabled once with root privileges. Without it, Scalene falls back to the utilisation of the whole G

正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。

打开原文
去社区讨论 →