2026-10-12 · ← News
Python 3.15 exposed a hidden bottleneck: 95.1% of samples ran off the main thread
Simon Willison used Python 3.15's new sampling profiler on web-edit and found that 95.1% of recorded stacks came from a worker thread. The example shows two things at once: the built-in profiler is practical, and a convincing visualization can miss almost all of the work when collection scope is wrong.
The image could not be loaded.
Simon Willison asked Codex with GPT-6 Astra Extra High to build a demonstration of Python 3.15's new sampling profiler against his web-edit application. The profile of the unmodified app captured 15,296 CPU stacks. preserve_endings appeared in 64.8% of them, while SequenceMatcher.find_longest_match accounted for 38.3% of leaf samples.
One option exposed 95.1% of the work in a worker thread
Sampling all threads was decisive. An AnyIO worker thread produced 95.1% of recorded stacks, so a main-thread-only profile would have missed the dominant cost. The result showed that preserving mixed line endings dominated a busy editing workload.
Python 3.15 adds the profiling package with two approaches. profiling.sampling periodically samples stacks and supports flame graphs, heatmaps, GIL analysis and attachment to a running process by PID. profiling.tracing observes every call, return and exception, providing exact call counts at the cost of higher overhead.
A standard-library profiler shortens the path from suspicion to code
A flame graph reveals the call hierarchy, while a source heatmap points to individual lines. A developer no longer needs to assemble several external tools before moving from a vague sense of slowness to a specific function. The sampling profiler can also attach to a live process without a restart or code changes.
Willison's workflow adds another layer. The coding agent was told to research the tool first and then create a reproducible demonstration. The agent did not pretend to infer performance from reading source code. It operated a measurement tool and returned artifacts that a person could inspect and challenge.
A colorful graph can answer the wrong question precisely
Sampling yields a statistical estimate, not an exact call count. Short functions can disappear between samples, and the documentation explicitly separates profiling from benchmarking. Tracing is available for exact call counts, while timeit is intended for timing small snippets.
The clearest warning is the 95.1% figure itself. A tool cannot compensate for the wrong threads, mode or workload. An agent can produce a flawless flame graph yet analyze a sideshow if a person fails to define a realistic scenario and inspect the collection scope.
A reproducible before-and-after profile will determine the value
The next step is not to admire the heatmap but to change preserve_endings and repeat the same workload. The benefit is established only if a new profile moves samples elsewhere without breaking line-ending correctness.
Teams should also watch whether an agent records the command, Python version, profiler parameters and test workload. Without those details, the picture is a diagnosis with no medical chart.
Lilith's verdict
The most impressive part of this flame graph is the work that would have vanished without all-thread sampling. An agent can hold the magnifying glass, but a person still decides where to point it.
I keep the external link at the end. First, a concise explanation here — no hunting across someone else's site.
Original source ↗ ↗