Pyinstrument: A Low Overhead Python Profiler That Doesn’t Get in Your Way
If you’ve ever tried profiling a Python application, you know the pain. The standard profilers (like cProfile) give you mountains of raw data, but figuring out what’s actually slow usually requires staring at call trees for way too long. And the overhead? It can change the behavior of your code, making the profile misleading.
Enter Pyinstrument. It’s a statistical profiler that records call stacks at intervals, not every single function call. That means much lower overhead and results that actually reflect how your code performs in real life. It outputs something you can actually read without a PhD in Python internals.
What It Does
Pyinstrument is a Python profiler that samples the call stack at a regular interval (default is every millisecond). Instead of instrumenting every function call, it takes snapshots of what the program is doing. This gives you a profile that shows you which functions are taking the most wall-clock time, not just CPU time. So if your code is waiting on I/O, network, or sleeping, Pyinstrument shows you that too.
It also handles multi-threaded programs decently, and it can profile code running in different interpreters like PyPy or even inside a Jupyter notebook.
Why It’s Cool
The big thing is the output format. Pyinstrument gives you a tree where the most time-consuming paths are collapsed and sorted. The default console output looks like a nice, clean list of “here’s where the time goes” rather than an endless wall of numbers. You get:
_ ._ __/__ _ _ _ _ _/_ Recorded: 16:45:42 /_//_/// /_\ / //_// / //_'/ // Duration: 1.23s
/ _/ v4.2.0 1.23s <module> __main__.py:1
├─ 0.84s some_slow_function my_module.py:10
│ ├─ 0.50s inner_loop my_module.py:15
│ └─ 0.34s another_call my_module.py:20
└─ 0.39s setup utils.py:5 └─ 0.39s read_file utils.py:12
It also supports rendering to HTML (with interactive flame charts), JSON, and even speedscope format for use with other tools.
Another neat feature: it works with async code. Because it’s sampling based, it doesn’t get confused by await points. That’s a real hassle with most profilers.
How to Try It
Install it with pip:
pip install pyinstrument
Then profile any script:
pyinstrument my_script.py
Or use it as a context manager in your code:
from pyinstrument import Profiler profiler = Profiler()
profiler.start() # Your code here profiler.stop()
print(profiler.output_text(unicode=True, color=True))