Finding Python Performance Bottlenecks
Profiling with cProfile
"cProfile (opens in a new tab) and profile provide deterministic profiling of Python programs. A profile is a set of statistics that describes how often and for how long various parts of the program executed. These statistics can be formatted into reports via the pstats (opens in a new tab) module."
Simple Approach
"""
Create a bottleneck
"""
import time
def initial_setup():
a = 7
time.sleep(1)
return a
def slow_function():
x = range(5)
for i in x:
a = initial_setup()
b = a + i
print(b)
"""
Use the simple `runcall` method that takes a function as an argument.
"""
import cProfile
profile = cProfile.Profile()
profile.runcall(slow_function)
"""
Pass the `profile` instance to the constructor of `Stats` to create a new instance of "ps".
We can then print the output to stdout.
"""
import pstats
ps = pstats.Stats(profile)
ps.print_stats()
Understanding the pstats output
Random listing order was used
ncalls tottime percall cumtime percall filename:lineno(function)
5 0.000 0.000 5.024 1.005 scratch_1.py:7(initial_setup)
1 0.000 0.000 5.024 5.024 scratch_1.py:13(slow_function)
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
5 0.000 0.000 0.000 0.000 {built-in method builtins.print}
5 5.024 1.005 5.024 1.005 {built-in method time.sleep}
Name | Description |
---|---|
ncalls | The number of times the analysed function has been called |
tottime | The total execution time spent in the analysed function (excluding the execution time of the subfunctions) |
percall | Tottime divided by ncalls |
cumtime | The total execution time spent in the analysed function (including the execution time of the subfunctions) |
percall | cumtime dived by ncalls |
filename:lineno(function) | file, line number and analysed function |
Profiling specific blocks of code
Using a context manager, the scope of the "profile" instance can be enclosed in a block
import cProfile
import pstats
import time
def initial_setup():
a = 7
time.sleep(1)
return a
def slow_function():
x = range(5)
for i in x:
a = initial_setup()
b = a + i
print(b)
with cProfile.Profile() as profile:
x = range(5)
for i in x:
a = initial_setup()
b = a + i
print(b)
ps = pstats.Stats(profile)
ps.sort_stats("tottime", "cumtime") # Sort by fields
ps.print_stats(3) # Limit output to top 3