cProfile measures where your program spends time. Before optimizing, profile—don't guess where the bottleneck is.
Command Line Profiling
# Profile a script
python -m cProfile script.py
# Sort by cumulative time
python -m cProfile -s cumtime script.py
# Save to file for analysis
python -m cProfile -o profile.stats script.pyBasic Python Usage
import cProfile
def slow_function():
total = 0
for i in range(1000000):
total += i
return total
# Profile a function
cProfile.run('slow_function()')Output Explained
4 function calls in 0.052 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.052 0.052 <string>:1(<module>)
1 0.052 0.052 0.052 0.052 script.py:3(slow_function)
1 0.000 0.000 0.052 0.052 {built-in method builtins.exec}
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
| Column | Meaning |
|---|---|
| ncalls | Number of calls |
| tottime | Time in function (excluding subcalls) |
| percall | tottime / ncalls |
| cumtime | Time in function (including subcalls) |
| percall | cumtime / ncalls |
Profile Object
import cProfile
import pstats
# Create profiler
profiler = cProfile.Profile()
# Profile code
profiler.enable()
result = slow_function()
profiler.disable()
# Get stats
stats = pstats.Stats(profiler)
stats.sort_stats('cumulative')
stats.print_stats(10) # Top 10 functionsContext Manager
import cProfile
import pstats
from contextlib import contextmanager
@contextmanager
def profile(sort_by='cumulative', limit=10):
profiler = cProfile.Profile()
profiler.enable()
try:
yield profiler
finally:
profiler.disable()
stats = pstats.Stats(profiler)
stats.sort_stats(sort_by)
stats.print_stats(limit)
# Usage
with profile():
result = complex_computation()Saving and Loading Stats
import cProfile
import pstats
# Save stats
cProfile.run('my_function()', 'output.stats')
# Load and analyze later
stats = pstats.Stats('output.stats')
stats.strip_dirs() # Remove directory paths
stats.sort_stats('cumtime') # Sort by cumulative time
stats.print_stats(20) # Print top 20
# Combine multiple runs
stats = pstats.Stats('run1.stats')
stats.add('run2.stats')
stats.add('run3.stats')
stats.print_stats()Filter Results
import pstats
stats = pstats.Stats('profile.stats')
# Filter by filename pattern
stats.print_stats('mymodule')
# Filter by function name
stats.print_stats('process')
# Multiple filters
stats.print_stats('mymodule', 'process')
# Regex patterns
stats.print_stats(r'.*\.py.*process')Call Graph Analysis
import pstats
stats = pstats.Stats('profile.stats')
# Show callers of a function
stats.print_callers('expensive_function')
# Show what a function calls
stats.print_callees('main')Sorting Options
import pstats
stats = pstats.Stats('profile.stats')
# Available sort keys
stats.sort_stats('calls') # Number of calls
stats.sort_stats('cumtime') # Cumulative time
stats.sort_stats('tottime') # Total time (excluding subcalls)
stats.sort_stats('time') # Alias for tottime
stats.sort_stats('filename') # File name
stats.sort_stats('name') # Function name
stats.sort_stats('nfl') # Name/file/lineDecorator for Profiling
import cProfile
import pstats
import io
from functools import wraps
def profile_func(func):
@wraps(func)
def wrapper(*args, **kwargs):
profiler = cProfile.Profile()
result = profiler.runcall(func, *args, **kwargs)
stream = io.StringIO()
stats = pstats.Stats(profiler, stream=stream)
stats.sort_stats('cumulative')
stats.print_stats(10)
print(stream.getvalue())
return result
return wrapper
@profile_func
def my_function():
# ... code to profile
passFinding Bottlenecks
import cProfile
import pstats
def find_bottlenecks(stats_file, threshold_percent=5):
"""Find functions taking more than threshold% of total time."""
stats = pstats.Stats(stats_file)
total_time = sum(stat[2] for stat in stats.stats.values()) # tottime
bottlenecks = []
for func, stat in stats.stats.items():
tottime = stat[2]
percent = (tottime / total_time) * 100
if percent >= threshold_percent:
bottlenecks.append((func, tottime, percent))
return sorted(bottlenecks, key=lambda x: -x[2])
# Usage
for func, time, percent in find_bottlenecks('profile.stats', 5):
print(f"{func[2]}: {time:.3f}s ({percent:.1f}%)")Line Profiler Alternative
cProfile profiles functions. For line-by-line profiling:
pip install line_profiler@profile # Decorator from line_profiler
def slow_function():
result = []
for i in range(1000):
result.append(i * 2)
return sum(result)kernprof -l -v script.pyMemory Profiling
cProfile tracks time, not memory. For memory:
pip install memory_profiler@profile # From memory_profiler
def memory_heavy():
data = [i ** 2 for i in range(1000000)]
return sum(data)Profiling in Tests
import cProfile
import pytest
@pytest.fixture
def profile():
profiler = cProfile.Profile()
profiler.enable()
yield profiler
profiler.disable()
profiler.print_stats(sort='cumtime')
def test_performance(profile):
result = function_under_test()
assert result == expected
# Profile printed after testVisualization
# Install visualization tool
pip install snakeviz
# Generate stats file
python -m cProfile -o profile.stats script.py
# Visualize in browser
snakeviz profile.statsCommon Patterns
# Profile web request handler
def profile_request(handler):
profiler = cProfile.Profile()
profiler.enable()
try:
return handler()
finally:
profiler.disable()
if should_log_profile():
save_profile(profiler)
# Profile with condition
def maybe_profile(func, profile_enabled=False):
if not profile_enabled:
return func()
profiler = cProfile.Profile()
result = profiler.runcall(func)
profiler.print_stats()
return resultQuick Reference
# Profile and sort by time
python -m cProfile -s tottime script.py
# Profile and save
python -m cProfile -o output.stats script.py
# Analyze saved stats
python -c "import pstats; pstats.Stats('output.stats').sort_stats('cumtime').print_stats(20)"| Goal | Command |
|---|---|
| Find slow functions | Sort by cumtime |
| Find CPU hogs | Sort by tottime |
| Find frequently called | Sort by calls |
| Focus on your code | Filter by module name |
| Visualize | Use snakeviz |
Profile first, optimize second. cProfile tells you exactly where to focus your optimization efforts.
React to this post: