A person holding a notebook plugs a cable into a bank of profiling machines showing a sampling timeline, a snapshot lever, waveform readouts, a flame graph, and a Python logo.

Python 3.15 Preview: Sampling Profiler

Python 3.15 ships with a brand-new profiling package defined in PEP 799. It finally retires the ancient profile module, gives cProfile a new home under profiling.tracing, and adds profiling.sampling, a statistical sampling profiler code-named Tachyon. You can point it at a script, a module, or even a live production process, and it’ll tell you where the time goes with virtually no overhead.

By the end of this tutorial, you’ll understand that:

  • Python 3.15 organizes its profilers under a new profiling package, deprecating the old profile module.
  • The new sampling profiler peeks at your program’s call stack from the outside, so your code runs at full speed while being profiled.
  • You can attach to a running process by its PID without restarting or modifying the target, as long as you have the right permissions.
  • Sampling modes let you separate CPU work from I/O waits and isolate code that runs while holding the GIL or handling an exception.
  • The profiler supports many output formats, including interactive flame graphs, line-level heatmaps, and a live top-like terminal dashboard.

First, you’ll get a quick refresher on how tracing and sampling profilers differ. Then, you’ll set up Python 3.15 with uv and profile a small 3D renderer with deliberately planted bottlenecks. Along the way, you’ll try out threads, async tasks, native-code boundaries, and the visualizations the profiler offers.

To get the most out of this tutorial, you should be comfortable running commands in a terminal because that’s where the profiler lives. It’ll also help if you’ve dabbled in threading or asyncio before, as you’ll profile both kinds of concurrency. Prior profiling experience is a plus, not a requirement. You’ll review the essential theory as you go.

Get to Know Python’s New profiling Package

For decades, Python has shipped two deterministic profilers in the standard library:

  1. profile
  2. cProfile

The first one is a pure-Python implementation that’s mostly of educational value, while the second one does the same job in the C programming language at a fraction of the cost. PEP 799 tidies up this corner in Python 3.15 by introducing a dedicated profiling package with two submodules:

Module What It Is
profiling.tracing The deterministic tracing profiler formerly known as cProfile
profiling.sampling The new statistical sampling profiler, code-named Tachyon

The old modules don’t disappear overnight, though. The cProfile module sticks around indefinitely as a thin alias, so your existing tooling won’t break. On the other hand, the pure-Python profile module enters its retirement. Importing it now triggers a warning, and Python 3.17 will remove it entirely:

Language: Python
>>> import profile
<python-input-0>:1: DeprecationWarning: The profile module is deprecated
⮑ and will be removed in Python 3.17. Use profiling.tracing (or cProfile)
⮑ for tracing profilers instead.

>>> import cProfile
>>> import profiling.tracing
>>> cProfile.Profile is profiling.tracing.Profile
True

The comparison in the last line proves that cProfile and profiling.tracing expose the very same class, so the two names are interchangeable. If you’d like a refresher on using the tracing profiler and interpreting its output, then check out Profiling in Python, which walks through cProfile and friends in detail.

The package reorganization in the standard library is the boring part of PEP 799. The interesting part is profiling.sampling, and that’s what you’ll spend the rest of this tutorial on.

Compare Tracing and Sampling Profilers

Before you fire up the new sampling profiler, it helps to know how it differs from its tracing counterpart. Both tools answer the same question, namely where your program spends most of its time. They just gather evidence in different ways. That difference dictates when you should reach for each of them. Here’s the thirty-second version of the theory before you dive deeper.

How Tracing Profilers Work

A deterministic tracing profiler hooks into the interpreter and registers a callback that runs on every single function call and return. Nothing escapes it. The resulting numbers are exact, down to how many times each function ran.

You can watch this happen below. Press play, and the profiler fires a hook on every call and return of a small demo program:

Interactive diagram — enable JavaScript to view.

That precision comes at a price. The constant interruptions can slow your program down severalfold and, worse, distort the measurements themselves. Cheap functions called millions of times suddenly look expensive because the profiler’s bookkeeping dwarfs their actual work. That’s why running a tracing profiler against a production workload is rarely an option.

How Sampling Profilers Work

A statistical sampling profiler takes the opposite approach. Instead of instrumenting your code, it periodically captures snapshots of the call stack—by default, a thousand times per second—and counts which functions it catches in the act. Functions that consume the most time will statistically appear in the most samples.

Locked learning resources

Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Article

Already a member? Sign-In

Locked learning resources

The full article is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Article

Already a member? Sign-In

About Bartosz Zaczyński

Bartosz is an experienced software engineer and Python educator with an M.Sc. in Applied Computer Science.

» More about Bartosz

Each tutorial at Real Python is created by a team of developers so that it meets our high quality standards. The team members who worked on this tutorial are:

What Do You Think?

What’s your #1 takeaway or favorite thing you learned? How are you going to put your newfound skills to use? Leave a comment below and let us know.

Commenting Tips: The most useful comments are those written with the goal of learning from or helping out other students. Get tips for asking good questions and get answers to common questions in our support portal.


Looking for a real-time conversation? Visit the Real Python Community Chat or join the next “Office Hours” Live Q&A Session. Happy Pythoning!

Become a Member to join the conversation.

Keep Learning

Related Topics: advanced python