Explain Codes LogoExplain Codes Logo

How do I measure elapsed time in Python?

python
timing
performance
best-practices
Anton ShumikhinbyAnton Shumikhin·Aug 16, 2024
TLDR

To monitor execution time to the highest precision, Python's time module and its rich time.perf_counter() function come in handy. Before starting your code, record the start time, post execution, calculate the difference between the end and start time to fetch the time spent.

import time start_time = time.perf_counter() # Insert your code here elapsed_time = time.perf_counter() - start_time print(f"Time spent: {elapsed_time:.4f} seconds")

This results in a high precision measurement of elapsed time, making it perfect for performance testing. It spares the time during sleep and also considers background processes.

Deeper Understanding: Timing verbosity

When you want the focusing solely on the CPU cycles, namely ignoring I/O operations API like sleep, time.process_time() becomes your friend. This way you get a better picture of pure computational time:

import time # Record CPU Start Time start_cpu_time = time.process_time() # CPU dependant code goes here # Take the difference cpu_elapsed_time = time.process_time() - start_cpu_time # Print using f-string print(f"CPU time spent: {cpu_elapsed_time:.4f} seconds")

Contrarily, it's worth noting that time.clock() has become an artifact of the past, and is obsolete!

When precision is not the primary focus, yet timing is yet required, time.time() falls into the grace. It becomes a choice for timing operations where the significant figures of time.perf_counter() are non-impactful:

import time # Compute the starting time start = time.time() # Code whose timing is not as important goes here # Compute the difference end = time.time() # Lazy printing print(f"Elapsed time: {end - start} seconds. It's tea time!")

timeit.default_timer(): A Helper for Accurate Code snippet Timing

In need of high accuracy timing for bite-sized code snippets, an essential tool in Python developers' kit is timeit.default_timer(). It auto collects the best precise timer available. No more guesswork for you!

from timeit import default_timer as timer # Start the timer, no need to say "On your marks" start = timer() # Code to benchmark # It's a wrap! Get the end time end = timer() # Print the time. Isn't this pythonic timing? print(f"Time taken: {end - start} seconds. It's faster than sipping a coffee!")

In Python 3.3 or later, timeit.default_timer() defaults to time.perf_counter(), aiding precise timings straight out of the box.

Beautiful Representation of Elapsed Time

When readability is utmost, making elapsed time human-friendly becomes essential. You may use datetime.timedelta to please the human eyes and brain:

from datetime import timedelta import time start = time.perf_counter() # Code to time goes here end = time.perf_counter() # Represent elapsed time beautifully elapsed_time = timedelta(seconds=end-start) print(f"Readable elapsed time: {elapsed_time}. Just enjoy the precision!")

This results in time displayed as hours:minutes:seconds.microseconds, much more digestible.

Crucial Aspects of Timing

Keep timing your code, remembering to exclude print statements and other I/O operations which can introduce variance in measurements:

import time # Start the clock start_time = time.perf_counter() # Compute-intensive code goes here # Get the time difference elapsed_time = time.perf_counter() - start_time # Now it's time to bring in the I/O operations, post the timing print(f"Computation time: {elapsed_time} seconds. Compute faster, I'm feeling sleepy!") print("Now, let's wake the I/O operations...")

For single-run time measurements, default_timer usually surpasses multiple runs with timeit.timeit() in accuracy. However, reliable repetitive sampling with timeit can lead to more statistically valid benchmarks.

Practical Scenarios and Timing Pitfalls

Systematic Timing vs Pure Computing Time

Depending on what you're monitoring:

  • System-wide timing inclusive of I/O waiting time is grasped better with time.perf_counter() as it provides real-time elapsed measurement.
  • For extracting pure computational time, devoid of sleep time or other interference, time.process_time() is more accurate.

Timing Loops and Recurring Tasks

In scenarios where you're timing loops or iterative tasks, you may want to calculate the cumulative time or time for individual iterations:

import time # Brace yourself, it's timing time! cumulative_time = 0 for i in range(num_iterations): # Get the start time start = time.perf_counter() # Insert the code for each iteration here # Compute the difference end = time.perf_counter() cumulative_time += (end - start) # Print the total time, don't you love f-strings? print(f"Clocking total time for all iterations: {cumulative_time} seconds. Congrats, you just timed a loop!")

Challenges in Optimization

While working for speeding up the code:

  • Resources like scikit-learn's "How to optimize for speed" can be a handy guide.
  • Profiling tools such as cProfile might be worth consideration to pinpoint bottlenecks.
  • Reminisce the tradeoff between efforts pumped into optimization against maintainability. Often the most significant speedup originates from algorithmic tweaks rather than micro-optimizations.