NeuroAgent

How to Measure Execution Time in Python

Learn the correct ways to measure execution time in Python. Discover why timeit.timeit() fails and explore precise timing methods like perf_counter(), timeit module usage, and best practices for accurate performance measurement.

Question

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:

python
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?

NeuroAgent

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

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:

python
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 timing
  • timer: The timer function to use
  • number: 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.

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

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

python
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:

python
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:

python
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:

python
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+)

python
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

python
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:

python
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:

python
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:

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

python
# 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.

python
# 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.

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

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

python
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:

python
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

  1. Python Documentation - time Module - Official Python documentation for timing functions
  2. Python Documentation - timeit Module - Official documentation for the timeit module
  3. Real Python - Python Timer Functions - Comprehensive guide to timing functions in Python
  4. Stack Overflow - How to measure time taken by a function to execute - Community insights on timing approaches
  5. GeeksforGeeks - Measure time taken by a program in Python - Tutorial with practical examples
  6. Python Performance - Timing and Profiling - Advanced timing techniques and best practices