How do I measure elapsed time in Python?
I want to measure the time it took to execute a function. I couldn’t get timeit to work:
import timeit
start = timeit.timeit()
print("hello")
end = timeit.timeit()
print(end - start)
What’s the correct way to measure execution time in Python, and why isn’t my current approach with timeit working?
The timeit.timeit() function requires a statement to time as a string parameter, which is why your current approach fails. The correct way to measure execution time in Python involves using timers like time.perf_counter() for high-precision measurements or the timeit module properly by passing code strings to time.
- Why Your Current Approach Fails
- Correct Methods for Measuring Elapsed Time
- Using the timeit Module Properly
- Advanced Timing Techniques
- Best Practices for Performance Measurement
- Common Pitfalls and How to Avoid Them
Why Your Current Approach Fails
Your current code fails because timeit.timeit() is designed to execute and time a specific statement provided as a string, not to work as a stopwatch function. When called without arguments, it raises a TypeError because it expects at least one positional argument representing the code to time.
The timeit.timeit() function has this signature:
timeit.timeit(stmt='pass', setup='pass', timer=<default timer>, number=1000000)
stmt: The statement to be timed (as a string)setup: Code to run once before timingtimer: The timer function to usenumber: Number of times to execute the statement
When you call timeit.timeit() without arguments, it’s equivalent to trying to time an empty statement, which isn’t valid.
Correct Methods for Measuring Elapsed Time
Using time.perf_counter()
The time.perf_counter() function provides the highest resolution timer available on your system and is ideal for measuring short durations.
import time
def my_function():
time.sleep(0.1) # Simulate some work
return "Done"
# Measure execution time
start = time.perf_counter()
result = my_function()
end = time.perf_counter()
elapsed_time = end - start
print(f"Function executed in {elapsed_time:.6f} seconds")
print(f"Result: {result}")
Using time.time()
The time.time() function returns the current time in seconds since the epoch. It’s less precise than perf_counter() but useful for longer durations.
import time
start = time.time()
# Your code here
time.sleep(0.1)
end = time.time()
elapsed_time = end - start
print(f"Elapsed time: {elapsed_time:.6f} seconds")
Using time.process_time()
This function measures CPU time (system and user time) rather than wall-clock time, which is useful when you want to measure actual CPU usage rather than waiting time.
import time
start = time.process_time()
# Your code here
time.sleep(0.1) # This won't be counted in process_time
end = time.process_time()
elapsed_time = end - start
print(f"CPU time used: {elapsed_time:.6f} seconds")
Using the timeit Module Properly
Basic timeit Usage
To time a single statement with timeit, pass it as a string:
import timeit
# Time a simple statement
execution_time = timeit.timeit('"-".join(str(n) for n in range(100))', number=1000)
print(f"Execution time: {execution_time:.6f} seconds")
Timing a Function with timeit
To time your specific function with timeit:
import timeit
def my_function():
time.sleep(0.1)
return "Done"
# Time the function call
execution_time = timeit.timeit('my_function()', setup='from __main__ import my_function', number=10)
print(f"Average execution time: {execution_time/10:.6f} seconds")
Using timeit.Timer Class
For more complex timing scenarios:
import timeit
def my_function():
time.sleep(0.1)
return "Done"
# Create a timer object
timer = timeit.Timer(stmt='my_function()', setup='from __main__ import my_function')
# Run the timer
execution_time = timer.timeit(number=10)
print(f"Average execution time: {execution_time/10:.6f} seconds")
Advanced Timing Techniques
Context Manager Approach (Python 3.3+)
from contextlib import contextmanager
import time
@contextmanager
def timer(name):
start = time.perf_counter()
yield
end = time.perf_counter()
print(f"{name}: {end - start:.6f} seconds")
# Usage
with timer("my_function"):
def my_function():
time.sleep(0.1)
result = my_function()
Decorator Approach
import time
import functools
def timer_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
end = time.perf_counter()
print(f"{func.__name__} executed in {end - start:.6f} seconds")
return result
return wrapper
# Usage
@timer_decorator
def my_function():
time.sleep(0.1)
return "Done"
result = my_function()
Profiling with cProfile
For comprehensive performance analysis:
import cProfile
import time
def my_function():
time.sleep(0.1)
# Some computation
sum(range(1000))
# Profile the function
cProfile.run('my_function()', sort='cumulative')
Best Practices for Performance Measurement
Choosing the Right Timer
| Timer | Precision | Use Case | Platform Independence |
|---|---|---|---|
time.perf_counter() |
Highest | Short durations, benchmarks | ✅ Yes |
time.time() |
Medium | Long durations, timestamps | ✅ Yes |
time.monotonic() |
High | Durations, unaffected by system time changes | ✅ Yes |
time.process_time() |
High | CPU usage measurement | ✅ Yes |
Multiple Measurements
For reliable results, always run multiple measurements:
import time
def measure_execution(func, number=5):
times = []
for _ in range(number):
start = time.perf_counter()
func()
end = time.perf_counter()
times.append(end - start)
return times
def my_function():
time.sleep(0.1)
times = measure_execution(my_function)
average_time = sum(times) / len(times)
print(f"Times: {times}")
print(f"Average: {average_time:.6f} seconds")
Excluding Setup Time
When using timeit, ensure setup code isn’t included in your timing:
import timeit
# Incorrect - setup is timed
execution_time = timeit.timeit('time.sleep(0.1)', number=10)
# Correct - setup is excluded
execution_time = timeit.timeit('time.sleep(0.1)',
setup='import time',
number=10)
Common Pitfalls and How to Avoid Them
1. Using Inappropriate Timers
Pitfall: Using time.time() for short durations or benchmarking.
Solution: Use time.perf_counter() for high-precision measurements.
# Bad - lower precision
start = time.time()
# Your code
end = time.time()
# Good - higher precision
start = time.perf_counter()
# Your code
end = time.perf_counter()
2. Including I/O Operations
Pitfall: Measuring including I/O operations that can be affected by external factors.
Solution: Separate I/O from computation when possible.
# Bad - includes I/O
start = time.perf_counter()
with open('file.txt', 'w') as f:
f.write("data")
end = time.perf_counter()
# Better - separate concerns
start = time.perf_counter()
# Computation only
end = time.perf_counter()
3. Not Accounting for Warm-up Effects
Pitfall: Measuring only once, which might include JIT compilation warm-up time.
Solution: Run multiple measurements and discard outliers.
import time
import statistics
def benchmark(func, runs=10):
times = []
for _ in range(runs):
start = time.perf_counter()
func()
end = time.perf_counter()
times.append(end - start)
# Remove outliers (first and last 10%)
sorted_times = sorted(times)
trimmed = sorted_times[int(len(sorted_times) * 0.1):-int(len(sorted_times) * 0.1)]
return statistics.mean(trimmed)
def my_function():
time.sleep(0.1)
avg_time = benchmark(my_function)
print(f"Average execution time: {avg_time:.6f} seconds")
4. Not Disabling Garbage Collection
Pitfall: Garbage collection affecting timing results.
Solution: Disable GC during timing if needed.
import time
import gc
def my_function():
# Create some objects to be garbage collected
[object() for _ in range(1000)]
# With garbage collection
start = time.perf_counter()
my_function()
end = time.perf_counter()
print(f"With GC: {end - start:.6f} seconds")
# Without garbage collection
gc.disable()
start = time.perf_counter()
my_function()
end = time.perf_counter()
print(f"Without GC: {end - start:.6f} seconds")
gc.enable()
5. Not Considering System Load
Pitfall: Measuring on a busy system with other processes affecting results.
Solution: Run measurements multiple times and on a quiet system.
import time
import os
def is_system_busy():
# Simple check - adjust based on your system
load_avg = os.getloadavg()[0] if hasattr(os, 'getloadavg') else 0
return load_avg > 2.0
def measure_on_quiet_system(func, max_attempts=5):
for attempt in range(max_attempts):
if not is_system_busy():
start = time.perf_counter()
func()
end = time.perf_counter()
return end - start
time.sleep(1)
raise RuntimeError("System too busy for accurate measurement")
def my_function():
time.sleep(0.1)
execution_time = measure_on_quiet_system(my_function)
print(f"Execution time: {execution_time:.6f} seconds")
Conclusion
Measuring execution time in Python requires understanding the different timing functions available and choosing the appropriate one for your use case. Your original timeit approach failed because the function expects a statement to time as a string parameter, not to work as a stopwatch.
Key takeaways:
- Use
time.perf_counter()for high-precision measurements of short durations - Use
timeit.timeit()correctly by passing statements as strings with proper setup - Consider using context managers or decorators for reusable timing code
- Always run multiple measurements and account for system factors
- Choose the right timer based on your precision requirements and what you’re measuring
For measuring your specific function, the simplest correct approach would be:
import time
def my_function():
time.sleep(0.1)
return "Done"
start = time.perf_counter()
result = my_function()
end = time.perf_counter()
print(f"Function executed in {end - start:.6f} seconds")
print(f"Result: {result}")
Sources
- Python Documentation - time Module - Official Python documentation for timing functions
- Python Documentation - timeit Module - Official documentation for the timeit module
- Real Python - Python Timer Functions - Comprehensive guide to timing functions in Python
- Stack Overflow - How to measure time taken by a function to execute - Community insights on timing approaches
- GeeksforGeeks - Measure time taken by a program in Python - Tutorial with practical examples
- Python Performance - Timing and Profiling - Advanced timing techniques and best practices