How to Measure Execution Time in Python: Vanilla Python, FastAPI, Flask, and Django
Measuring how long your Python code takes to run is a critical skill for performance optimization, benchmarking, and identifying bottlenecks in production applications. Depending on whether you are analyzing a single micro-operation, profiling a complex function, or tracking HTTP request-response lifecycles, Python offers a wide variety of tools.
This guide provides a comprehensive breakdown of how to measure execution time in vanilla Python, followed by specialized implementations for the three major web frameworks: FastAPI, Flask, and Django.
Measuring Execution Time in Vanilla Python
1. High-Precision Timing with time.perf_counter()
For measuring execution time of a code block or function, use time.perf_counter(). It uses a monotonic clock with the highest available resolution on your system, unaffected by system clock adjustments.
import time
def slow_calculation():
return sum(range(10**7))
start = time.perf_counter()
slow_calculation()
end = time.perf_counter()
elapsed = end - start
print(f"Executed in: {elapsed:.6f} seconds")
Note: Avoid time.time() for performance benchmarking, as it is low-resolution and can be affected by system clock sync operations.
2. Measuring CPU-Only Time with time.process_time()
If your code spends a lot of time waiting for I/O (like database queries or external API calls) and you only want to measure the active CPU time consumed by your process:
start = time.process_time()
# CPU-heavy operations here
end = time.process_time()
cpu_time = end - start
print(f"Active CPU Time: {cpu_time:.6f} seconds")
3. Reusable Timer Context Manager
A custom context manager is a clean, Pythonic way to time arbitrary blocks of code without writing boilerplate code repeatedly:
import time
class Timer:
def __enter__(self):
self.start = time.perf_counter()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.end = time.perf_counter()
self.elapsed = self.end - self.start
print(f"Elapsed time: {self.elapsed:.6f} seconds")
# Usage:
with Timer():
# Code to measure
_ = [i ** 2 for i in range(10**6)]
4. Micro-benchmarking with the timeit Module
To compare different micro-operations (e.g. list comprehension vs. map), use the timeit module. It runs the code thousands of times to get statistically accurate averages and automatically turns off garbage collection to avoid noise.
import timeit
# Code snippet to run
setup_code = "import math"
test_code = "math.sqrt(25)"
# Get execution time for 100,000 runs
elapsed = timeit.timeit(stmt=test_code, setup=setup_code, number=100000)
print(f"Average time per run: {elapsed / 100000:.8f} seconds")
Measuring Execution Time in FastAPI
FastAPI handles asynchronous code natively. You can measure execution time globally using middleware or selectively using async decorators.
1. Global Performance Logging (HTTP Middleware)
To track the response time of every API request and append it as a custom response header:
import time
from fastapi import FastAPI, Request
app = FastAPI()
@app.middleware("http")
async def log_execution_time(request: Request, call_next):
start_time = time.perf_counter()
response = await call_next(request)
end_time = time.perf_counter()
execution_time_ms = (end_time - start_time) * 1000
response.headers["X-Process-Time-Ms"] = f"{execution_time_ms:.2f}"
print(f"Request: {request.method} {request.url.path} took {execution_time_ms:.2f}ms")
return response
2. Async Endpoint Decorator
For timing specific asynchronous endpoints without altering their core code:
import functools
def measure_async_time(func):
@functools.wraps(func)
async def wrapper(*args, **kwargs):
start = time.perf_counter()
result = await func(*args, **kwargs)
end = time.perf_counter()
print(f"{func.__name__} took {(end - start) * 1000:.2f}ms")
return result
return wrapper
@app.get("/items/{item_id}")
@measure_async_time
async def read_item(item_id: int):
return {"id": item_id}
Measuring Execution Time in Flask
In Flask, you can intercept the request cycle using application hooks or custom decorators.
1. Global Timing with before_request and after_request
Flask provides g as a context-local storage object bound to the active request. You can store the start time on g and retrieve it when sending the response:
from flask import Flask, g, request
app = Flask(__name__)
@app.before_request
def start_timer():
g.start_time = time.perf_counter()
@app.after_request
def log_timer(response):
if hasattr(g, 'start_time'):
elapsed = (time.perf_counter() - g.start_time) * 1000
response.headers["X-Process-Time-Ms"] = f"{elapsed:.2f}"
print(f"Flask path '{request.path}' took {elapsed:.2f}ms")
return response
2. Endpoint Decorator
import functools
def measure_flask_time(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
end = time.perf_counter()
print(f"{func.__name__} view took {(end - start) * 1000:.2f}ms")
return result
return wrapper
@app.route("/hello")
@measure_flask_time
def hello():
return "Hello World!"
Measuring Execution Time in Django
In Django, custom middleware classes wrap the entire request-response process.
1. Global Django Middleware
Create a file named middleware.py inside your app directory:
import time
class ExecutionTimeMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
start = time.perf_counter()
response = self.get_response(request)
end = time.perf_counter()
elapsed = (end - start) * 1000
response["X-Process-Time-Ms"] = f"{elapsed:.2f}"
print(f"Django request to '{request.path}' took {elapsed:.2f}ms")
return response
Add your middleware to the MIDDLEWARE list in settings.py:
# settings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
# ...
'your_app.middleware.ExecutionTimeMiddleware',
]
2. View Decorator
import functools
def measure_django_view_time(view_func):
@functools.wraps(view_func)
def wrapper(request, *args, **kwargs):
start = time.perf_counter()
response = view_func(request, *args, **kwargs)
end = time.perf_counter()
print(f"View '{view_func.__name__}' ran in {(end - start) * 1000:.2f}ms")
return response
return wrapper
Sources
- [1] Python Documentation: Time Monotonic Clocks
- [2] Python Documentation: timeit Benchmarking
- [3] FastAPI Documentation: Custom Middleware
- [4] Flask Documentation: Request Lifecycle Callback Hooks
- [5] Django Documentation: Writing custom middleware
