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.py

Basic 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}
ColumnMeaning
ncallsNumber of calls
tottimeTime in function (excluding subcalls)
percalltottime / ncalls
cumtimeTime in function (including subcalls)
percallcumtime / 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 functions

Context 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/line

Decorator 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
    pass

Finding 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.py

Memory 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 test

Visualization

# Install visualization tool
pip install snakeviz
 
# Generate stats file
python -m cProfile -o profile.stats script.py
 
# Visualize in browser
snakeviz profile.stats

Common 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 result

Quick 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)"
GoalCommand
Find slow functionsSort by cumtime
Find CPU hogsSort by tottime
Find frequently calledSort by calls
Focus on your codeFilter by module name
VisualizeUse snakeviz

Profile first, optimize second. cProfile tells you exactly where to focus your optimization efforts.

React to this post: