Pprofile
Line-granularity, thread-aware deterministic and statistic pure-python profiler
Install / Use
/learn @vpelletier/PprofileREADME
Line-granularity, thread-aware deterministic and statistic pure-python profiler
Inspired from Robert Kern's line_profiler_ .
Usage
As a command::
$ pprofile some_python_executable arg1 ...
Once some_python_executable returns, prints annotated code of each file
involved in the execution.
As a command, ignoring any files from default sys.path (ie, python modules
themselves), for shorter output::
$ pprofile --exclude-syspath some_python_executable arg1 ...
Executing a module, like :code:python -m. --exclude-syspath is not
recommended in this mode, as it will likely hide what you intend to profile.
Also, explicitly ending pprofile arguments with -- will prevent accidentally
stealing command's arguments::
$ pprofile -m some_python_module -- arg1 ...
As a module:
.. code:: python
import pprofile
def someHotSpotCallable(): # Deterministic profiler prof = pprofile.Profile() with prof(): # Code to profile prof.print_stats()
def someOtherHotSpotCallable(): # Statistic profiler prof = pprofile.StatisticalProfile() with prof( period=0.001, # Sample every 1ms single=True, # Only sample current thread ): # Code to profile prof.print_stats()
For advanced usage, see :code:pprofile --help and :code:pydoc pprofile.
Profiling overhead
pprofile default mode (Deterministic profiling) has a large overhead.
Part of the reason being that it is written to be as portable as possible
(so no C extension). This large overhead can be an issue, which can be
avoided by using Statistic profiling at the cost of some result
readability decrease.
Rule of thumb:
+-----------------------------+----------------------------+------------------------+
| Code to profile runs for... | Deterministic profiling_ | Statistic profiling_ |
+=============================+============================+========================+
| a few seconds | Yes | No [#]_ |
+-----------------------------+----------------------------+------------------------+
| a few minutes | Maybe | Yes |
+-----------------------------+----------------------------+------------------------+
| more (ex: daemon) | No | Yes [#]_ |
+-----------------------------+----------------------------+------------------------+
Once you identified the hot spot and you decide you need finer-grained profiling to understand what needs fixing, you should try to make to-profile code run for shorter time so you can reasonably use deterministic profiling: use a smaller data set triggering the same code path, modify the code to only enable profiling on small pieces of code...
.. [#] Statistic profiling will not have time to collect enough samples to produce usable output.
.. [#] You may want to consider triggering pprofile from
a signal handler or other IPC mechanism to profile
a shorter subset. See zpprofile.py for how it can
be used to profile code inside a running (zope)
service (in which case the IPC mechanism is just
Zope normal URL handling).
Output
Supported output formats.
Callgrind
The most useful output mode of pprofile is Callgrind Profile Format, allows
browsing profiling results with kcachegrind (or qcachegrind_ on Windows).
::
$ pprofile --format callgrind --out cachegrind.out.threads demo/threads.py
Callgrind format is implicitly enabled if --out basename starts with
cachegrind.out., so above command can be simplified as::
$ pprofile --out cachegrind.out.threads demo/threads.py
If you are analyzing callgrind traces on a different machine, you may want to
use the --zipfile option to generate a zip file containing all files::
$ pprofile --out cachegrind.out.threads --zipfile threads_source.zip demo/threads.py
Generated files will use relative paths, so you can extract generated archive in the same path as profiling result, and kcachegrind will load them - and not your system-wide files, which may differ.
Annotated code
Human-readable output, but can become difficult to use with large programs.
::
$ pprofile demo/threads.py
Profiling modes
Deterministic profiling
In deterministic profiling mode, pprofile gets notified of each executed line. This mode generates very detailed reports, but at the cost of a large overhead. Also, profiling hooks being per-thread, either profiling must be enable before spawning threads (if you want to profile more than just the current thread), or profiled application must provide ways of enabling profiling afterwards
- which is not very convenient.
::
$ pprofile --threads 0 demo/threads.py Command line: ['demo/threads.py'] Total duration: 1.00573s File: demo/threads.py File duration: 1.00168s (99.60%) Line #| Hits| Time| Time per hit| %|Source code ------+----------+-------------+-------------+-------+----------- 1| 2| 3.21865e-05| 1.60933e-05| 0.00%|import threading 2| 1| 5.96046e-06| 5.96046e-06| 0.00%|import time 3| 0| 0| 0| 0.00%| 4| 2| 1.5974e-05| 7.98702e-06| 0.00%|def func(): 5| 1| 1.00111| 1.00111| 99.54%| time.sleep(1) 6| 0| 0| 0| 0.00%| 7| 2| 2.00272e-05| 1.00136e-05| 0.00%|def func2(): 8| 1| 1.69277e-05| 1.69277e-05| 0.00%| pass 9| 0| 0| 0| 0.00%| 10| 1| 1.81198e-05| 1.81198e-05| 0.00%|t1 = threading.Thread(target=func) (call)| 1| 0.000610828| 0.000610828| 0.06%|# /usr/lib/python2.7/threading.py:436 init 11| 1| 1.52588e-05| 1.52588e-05| 0.00%|t2 = threading.Thread(target=func) (call)| 1| 0.000438929| 0.000438929| 0.04%|# /usr/lib/python2.7/threading.py:436 init 12| 1| 4.79221e-05| 4.79221e-05| 0.00%|t1.start() (call)| 1| 0.000843048| 0.000843048| 0.08%|# /usr/lib/python2.7/threading.py:485 start 13| 1| 6.48499e-05| 6.48499e-05| 0.01%|t2.start() (call)| 1| 0.00115609| 0.00115609| 0.11%|# /usr/lib/python2.7/threading.py:485 start 14| 1| 0.000205994| 0.000205994| 0.02%|(func(), func2()) (call)| 1| 1.00112| 1.00112| 99.54%|# demo/threads.py:4 func (call)| 1| 3.09944e-05| 3.09944e-05| 0.00%|# demo/threads.py:7 func2 15| 1| 7.62939e-05| 7.62939e-05| 0.01%|t1.join() (call)| 1| 0.000423908| 0.000423908| 0.04%|# /usr/lib/python2.7/threading.py:653 join 16| 1| 5.26905e-05| 5.26905e-05| 0.01%|t2.join() (call)| 1| 0.000320196| 0.000320196| 0.03%|# /usr/lib/python2.7/threading.py:653 join
Note that time.sleep call is not counted as such. For some reason, python is not generating c_call/c_return/c_exception events (which are ignored by current code, as a result).
Statistic profiling
In statistic profiling mode, pprofile periodically snapshots the current callstack(s) of current process to see what is being executed. As a result, profiler overhead can be dramatically reduced, making it possible to profile real workloads. Also, as statistic profiling acts at the whole-process level, it can be toggled independently of profiled code.
The downside of statistic profiling is that output lacks timing information, which makes it harder to understand.
::
$ pprofile --statistic .01 demo/threads.py Command line: ['demo/threads.py'] Total duration: 1.0026s File: demo/threads.py File duration: 0s (0.00%) Line #| Hits| Time| Time per hit| %|Source code ------+----------+-------------+-------------+-------+----------- 1| 0| 0| 0| 0.00%|import threading 2| 0| 0| 0| 0.00%|import time 3| 0| 0| 0| 0.00%| 4| 0| 0| 0| 0.00%|def func(): 5| 288| 0| 0| 0.00%| time.sleep(1) 6| 0| 0| 0| 0.00%| 7| 0| 0| 0| 0.00%|def func2(): 8| 0| 0| 0| 0.00%| pass 9| 0| 0| 0| 0.00%| 10| 0| 0| 0| 0.00%|t1 = threading.Thread(target=func) 11| 0| 0| 0| 0.00%|t2 = threading.Thread(target=func) 12| 0| 0| 0| 0.00%|t1.start() 13| 0| 0| 0| 0.00%|t2.start() 14| 0| 0| 0| 0.00%|(func(), func2()) (call)| 96| 0| 0| 0.00%|# demo/threads.py:4 func 15| 0| 0| 0| 0.00%|t1.join() 16| 0| 0| 0| 0.00%|t2.join() File: /usr/lib/python2.7/threading.py File duration: 0s (0.00%) Line #| Hits| Time| Time per hit| %|Source code ------+----------+-------------+-------------+-------+----------- [...] 308| 0| 0| 0| 0.00%| def wait(self, timeout=None): [...] 338| 0| 0| 0| 0.00%| if timeout is None: 339| 1| 0| 0| 0.00%| waiter.acquire() 340| 0| 0| 0| 0.00%| if debug: [...] 600| 0| 0| 0| 0.00%| def wait(self, timeout=None): [...] 617| 0| 0| 0| 0.00%| if not self.__flag: 618| 0|
Related Skills
node-connect
344.1kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
96.8kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
344.1kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
344.1kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
