cProfile
cProfile is a function-level profiler that is part of Python’s standard library. It is a command-line profiler, that can also be called directly from Python. 3rd party packages such as snakeviz can be used to visualise it’s output in a web browser or Jupyter Notebook.
QuickStart
It can be called directly within your Python code as an imported package, however it’s easier to use it’s script interface:
python -m cProfile <script name> <arguments> > <output file>
For example if you normally run your program as:
python my_script.py input.csv
You would call cProfile to produce profiling output analysis.txt with:
python -m cProfile my_script.py input.csv > analysis.txt
No additional changes to your Python code are required, it’s really that simple!
Interpreting Output
The output file analysis.txt can be opened with a text editor, it contains a table that identifies functions in order of the proportion of execution time they occupied.
28 function calls in 4.754 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 4.754 4.754 my_script.py:1(<module>)
1 0.000 0.000 1.001 1.001 my_script.py:13(b_2)
3 0.000 0.000 1.513 0.504 my_script.py:16(c_1)
3 0.000 0.000 1.238 0.413 my_script.py:19(c_2)
3 0.000 0.000 0.334 0.111 my_script.py:23(d_1)
1 0.000 0.000 4.754 4.754 my_script.py:3(a_1)
3 0.000 0.000 2.751 0.917 my_script.py:9(b_1)
1 0.000 0.000 4.754 4.754 {built-in method builtins.exec}
11 4.753 0.432 4.753 0.432 {built-in method time.sleep}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
The columns have the following definitions:
| Column | Definition |
|---|---|
ncalls |
The number of times the given function was called. |
tottime |
The total time spent in the given function, excluding child function calls. |
percall |
The average tottime per function call (tottime/ncalls). |
cumtime |
The total time spent in the given function, including child function calls. |
percall |
The average cumtime per function call (cumtime/ncalls). |
filename:lineno(function) |
The location of the given function’s definition and it’s name. |
Visualising Output
For more complex programs the former output will grow and can become harder to interpret. Instead, an output file -o <output_file> can be passed to cProfile which can be interpreted by 3rd party packages such as snakeviz to provide an interactive visualisation.
First it’s necessary to install snakeviz, it should be available via pip.
pip install snakeviz
Then cProfile can be executed, specifying an output file with -o
python -m cProfile -o <output file> <script name> <arguments>
For example if you normally run your program as:
python my_script.py input.csv
You would call cProfile to produce profiling output out.prof with:
python -m cProfile -o out.prof my_script.py input.csv
Then out.prof is passed to snakeviz:
python -m snakeviz out.prof
This should open snakeviz’s visualisation of the profiling results in your web browser
snakeviz.The icicle diagram displayed by snakeviz represents an aggregate of the call stack during the execution of the profiled code.
The box which fills the top row represents the the root call, filling the row shows that it occupied 100% of the runtime.
The second row holds the child methods called from the root, with their widths relative to the proportion of runtime they occupied.
This continues with each subsequent row, however where a method only occupies 50% of the runtime, it’s children can only occupy a maximum of that runtime hence the appearance of “icicles” as each row gets narrower when the overhead of methods with no further children is accounted for.
By clicking a box within the diagram, it will “zoom” making the selected box the root allowing more detail to be explored. The diagram is limited to 10 rows by default (“Depth”) and methods with a relatively small proportion of the runtime are hidden (“Cutoff”).
As you hover each box, information to the left of the diagram updates specifying the location of the method and for how long it ran.
Jupyter Notebooks
If you’re more familiar with writing Python inside Jupyter notebooks you can still use snakeviz directly from inside notebooks using the notebooks “magic” prefix (%) and it will automatically call cProfile for you.
First snakeviz must be installed and it’s extension loaded.
!pip install snakeviz
%load_ext snakeviz
Following this, you can either call %snakeviz to profile a function defined earlier in the notebook.
%snakeviz my_function()
Or, you can create a %%snakeviz cell, to profile the python executed within it.
%%snakeviz
def my_function():
print("Hello World!")
my_function()
In both cases, the full snakeviz profile visualisation will appear as an output within the notebook!
You may wish to right click the top of the output, and select “Disable Scrolling for Outputs” to expand it’s box if it begins too small.
Limitations
cProfile is a deterministic profiler, rather than a sampling profiler, this means that it captures all function calls. This causes the profiler to have a high profiling overhead, which may lead to the profiling execution being significantly slower for code that includes many function calls.