Last Updated on September 29, 2023
You can benchmark Python code using the time.monotonic() function.
In this tutorial, you will discover how to benchmark Python code using the time.monotonic() function.
Let’s get started.
Need to Benchmark Python Code
Benchmarking Python code refers to comparing the performance of one program to variations of the program.
Benchmarking is the practice of comparing business processes and performance metrics to industry bests and best practices from other companies. Dimensions typically measured are quality, time and cost.
— Benchmarking, Wikipedia.
Typically, we make changes to the programs, such as adding concurrency, in order to improve the performance of the program on a given system.
Improving performance typically means reducing the run time of the program.
Therefore, when we benchmark programs in Python after adding concurrency, we typically are interested in recording how long a program takes to run.
It is critical to be systematic when benchmarking code.
The first step is to record how long an unmodified version of the program takes to run. This provides a baseline in performance to which all other versions of the program must be compared. If we are adding concurrency, then the unmodified version of the program will typically perform tasks sequentially, e.g. one-by-one.
We can then make modifications to the program, such as adding thread pools, process pools, or asyncio. The goal is to perform tasks concurrently (out of order), even in parallel (simultaneously). The performance of the program can be benchmarked and compared to the performance of the unmodified version.
The performance of the modified versions of the program must have better performance than the unmodified version of the program. If they do not, they are not improvements and should not be adopted.
How can we benchmark the performance of programs in Python?
Run loops using all CPUs, download your FREE book to learn how.
How to Benchmark with time.monotonic()
We can benchmark Python code using the time.monotonic() function.
This is a function that is provided in the time module and is part of the Python standard library.
It can be used to record the time before a piece of code that is being benchmarked, then again to record the time again after the benchmarked code. The difference between the two times can be reported as the relative execution time, e.g. compared to another benchmark.
Let’s take a closer look.
What is time.monotonic()
The time.monotonic() function returns time stamps from a clock that cannot go backwards, as its name suggests.
In mathematics, monotonic, e.g. a monotonic function means a function whose output increases (or decreases).
This means that the result from the time.monotonic() function will never be before the result from a prior call.
Return the value (in fractional seconds) of a monotonic clock, i.e. a clock that cannot go backwards.
— time — Time access and conversions
It is a high-resolution time stamp, although is not relative to epoch-like time.time(). Instead, like time.perf_counter() it uses a separate timer separate from the system clock.
The time.monotonic() has a lower resolution than the time.perf_counter() function.
This means that values from the time.monotonic() function can be compared to each other, relatively, but not to the system clock.
The clock is not affected by system clock updates. The reference point of the returned value is undefined, so that only the difference between the results of two calls is valid.
— time — Time access and conversions
Like the time.perf_counter() function, time.monotonic() function is “system-wide”, meaning that it is not affected by changes to the system clock, such as updates or clock adjustments due to time synchronization.
Like the time.perf_counter() function, the time.monotonic() function was introduced in Python version 3.3 with the intent of addressing the limitations of the time.time() function tied to the system clock, such as use in short-duration benchmarking.
monotonic(): Monotonic clock (cannot go backward), not affected by system clock updates.
— What’s New In Python 3.3
Why Not Benchmark With time.time()?
It is common to benchmark code with time.time() instead of the time.monotonic().
Nevertheless, the time.time() function has some limitations, such as:
- Lack of Precision: time.time() provides time measurements in seconds and fractions of a second, which might not be sufficient for accurately profiling very short code segments or fine-grained optimizations.
- Clock Drift and System Load: System clock drift or variations in system load can introduce inaccuracies in the measurements, leading to inconsistent results.
- Overhead: The overhead introduced by the time.time() calls themselves can be significant for small and fast operations, potentially skewing the results.
- System Clock Adjustments: The clock used by time.time() may be updated, such as when adjusted for leap seconds or synchronizing with a time sever.
The biggest limitation of time.time() is that it is possible for a subsequent time to be before a previous time. The time.time() function is not monotonic. This can be because of many factors such as rounding, system load, time synchronization, and lack of precision.
As such, time.time() is not appropriate for fine-grained benchmarking, such as benchmarking code that has a very short duration, e.g. milliseconds or less.
You can learn more about benchmarking with the time.time() function in the tutorial:
Difference between time.monotonic() and time.perf_counter()
Both time.perf_counter() and time.monotonic() are functions provided by Python’s time module for measuring time intervals and durations.
However, they serve slightly different purposes and have distinct characteristics:
1. Purpose:
- time.perf_counter(): This function returns a clock with the highest available resolution to measure a monotonic, continuous, and linearly increasing time. It’s well-suited for benchmarking, profiling, and measuring short durations with high precision. It includes time intervals during which the system is sleeping or in an idle state.
- time.monotonic(): This function also returns a monotonic clock, but it doesn’t guarantee high resolution. Its primary purpose is to measure time intervals in a way that’s immune to system clock adjustments (e.g., due to daylight saving time changes) and provides a reliable way to measure real-world time spans. It excludes time intervals when the system is sleeping or in an idle state.
2. Accuracy and Resolution:
- time.perf_counter(): This function provides the highest resolution available from the system’s hardware clock. It’s suitable for measuring very short durations and is highly accurate for benchmarking and profiling.
- time.monotonic(): While it’s also monotonic, the resolution of time.monotonic() might be lower than that of time.perf_counter(). It focuses more on providing a reliable representation of time intervals while being immune to system clock changes.
3. Use Cases:
- time.perf_counter(): Best suited for benchmarking, performance measurement, profiling, and any scenario where high-resolution timing is essential.
- time.monotonic(): Ideal for measuring real-world time intervals, intervals between events, and any situation where consistency is important.
If you need high-resolution timing for short durations and accurate performance measurements, time.perf_counter() is appropriate.
Where is time.monotonic() Used?
The time.monotonic() function is used in various parts of the Python standard library to ensure accurate and consistent time measurements that are immune to system clock adjustments.
Here are a few notable places where time.monotonic() is used:
- threading Module: In the threading module, time.monotonic() is used to implement the Thread.join(timeout) method. It provides an accurate timeout mechanism that doesn’t rely on the system clock and ensures threads are joined after the specified amount of real-world time.
- multiprocessing Module: Similar to the threading module, the multiprocessing module uses time.monotonic() for implementing timeouts and synchronization mechanisms in a way that’s independent of system clock adjustments.
- queue Module: The queue module uses time.monotonic() for timeout-related functionality when waiting for items to be added to or retrieved from a queue.
- asyncio Module: In the asyncio module, which provides asynchronous I/O support, time.monotonic() is used for measuring time intervals and timeouts in a way that’s consistent across tasks and independent of the system clock.
- sched Module: The sched module, which provides event scheduling, uses time.monotonic() to ensure accurate scheduling of events without being affected by changes in system time..
These are just a few examples of where time.monotonic() is used within the Python standard library. It’s a crucial tool for accurately measuring time intervals and durations in a wide range of scenarios, particularly when dealing with concurrency, synchronization, and real-world time measurements.
Now that we know about the time.monotonic() function, let’s look at how we can use it to benchmark our Python programs.
How to Benchmark with time.monotonic()
We can use the time.monotonic() function to benchmark Python code.
There are perhaps 3 case studies we may want to consider, they are:
- Benchmarking a Python statement.
- Benchmarking a Python function.
- Benchmarking a Python script (program).
Let’s look at how we can benchmark time.monotonic() function.
How to Benchmark a Python Statement
We can use the time.monotonic() function to benchmark arbitrary Python statements.
The procedure is as follows:
- Record time.monotonic() before the statement.
- Execute the statement.
- Record time.monotonic() after the statement.
- Subtract start time from after time to give duration.
- Report the duration using print().
For example:
1 2 3 4 5 6 7 8 9 10 |
# record start time time_start = monotonic() # execute the statement ... # record end time time_end = monotonic() # calculate the duration time_duration = time_end - time_start # report the duration print(f'Took {time_duration:.3f} seconds') |
How to Benchmark a Python Function
We can use the time.monotonic() function to benchmark arbitrary Python functions.
The procedure is as follows:
- Record time.monotonic() before the function.
- Call the function.
- Record time.monotonic() after the function.
- Subtract start time from after time to give duration.
- Report the duration using print().
For example:
1 2 3 4 5 6 7 8 9 10 |
# record start time time_start = monotonic() # call the function ... # record end time time_end = monotonic() # calculate the duration time_duration = time_end - time_start # report the duration print(f'Took {time_duration:.3f} seconds') |
How to Benchmark a Python Script
We can use the time.monotonic() function to benchmark arbitrary Python scripts (Python files).
It requires that the entry point into the script is first moved into a new function, that we will call main(). This is to make it easy for all code in the script to be wrapped in the benchmarking code.
The procedure is as follows:
- Move the entry of the script into a main() function (if needed).
- Record time.monotonic() before the main() function.
- Call the main() function.
- Record time.monotonic() after the main() function.
- Subtract start time from after time to give duration.
- Report the duration using print().
For example:
1 2 3 4 5 6 7 8 9 10 11 12 |
# protect the entry point if __name__ == '__main__': # record start time time_start = monotonic() # execute the script main() # record end time time_end = monotonic() # calculate the duration time_duration = time_end - time_start # report the duration print(f'Took {time_duration:.3f} seconds') |
Now that we know how to benchmark using the time.monotonic() function, let’s look at some worked examples.
Free Python Benchmarking Course
Get FREE access to my 7-day email course on Python Benchmarking.
Discover benchmarking with the time.perf_counter() function, how to develop a benchmarking helper function and context manager and how to use the timeit API and command line.
Example of Benchmarking a Statement with time.monotonic()
We can explore how to benchmark a Python statement using time.monotonic() with a worked example.
In this example, we will define a statement that creates a list of 100 million squared integers in a list comprehension, which should take a number of seconds.
1 2 3 |
... # execute the statement data = [i*i for i in range(100000000)] |
We will then surround this statement with benchmarking code.
Firstly, we will record the start time using the time.monotonic() function.
1 2 3 |
... # record start time time_start = monotonic() |
Afterward, we will record the end time, calculate the overall execution duration, and report the result.
1 2 3 4 5 6 7 |
... # record end time time_end = monotonic() # calculate the duration time_duration = time_end - time_start # report the duration print(f'Took {time_duration:.3f} seconds') |
Tying this together, the complete example is listed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# SuperFastPython.com # example of benchmarking a statement with time.monotonic() from time import monotonic # record start time time_start = monotonic() # execute the statement data = [i*i for i in range(100000000)] # record end time time_end = monotonic() # calculate the duration time_duration = time_end - time_start # report the duration print(f'Took {time_duration:.3f} seconds') |
Running the example first records the start time, a number from an internal clock.
Next, the Python statement is executed, in this case creating a list of 100 million squared integers.
The end time is then recorded, as a number from an internal clock.
The difference between the two recorded times is calculated, providing the statement execution duration in seconds.
Finally, the result is reported, truncated to three decimal places (milliseconds).
In this case, we can see that the statement took about 5.070 seconds to complete.
Note, the results on your system may vary.
This highlights how we can benchmark a Python statement using the time.monotonic() function.
1 |
Took 5.160 seconds |
Next, let’s explore an example of benchmarking a function using the time.monotonic() function.
Overwhelmed by the python concurrency APIs?
Find relief, download my FREE Python Concurrency Mind Maps
Example of Benchmarking a Function with time.monotonic()
We can explore how to benchmark a Python function using time.monotonic() with a worked example.
In this example, we will define a function that creates a list of 100 million squared integers in a list comprehension, which should take a number of seconds.
1 2 3 4 |
# function to benchmark def task(): # create a large list data = [i*i for i in range(100000000)] |
We will then call this function, and surround the function call with benchmarking code.
Firstly, we will record the start time using the time.monotonic() function.
1 2 3 |
... # record start time time_start = monotonic() |
Afterward, we will record the end time, calculate the overall execution duration, and report the result.
1 2 3 4 5 6 7 |
... # record end time time_end = monotonic() # calculate the duration time_duration = time_end - time_start # report the duration print(f'Took {time_duration:.3f} seconds') |
Tying this together, the complete example is listed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# SuperFastPython.com # example of benchmarking a function with time.monotonic() from time import monotonic # function to benchmark def task(): # create a large list data = [i*i for i in range(100000000)] # record start time time_start = monotonic() # execute the function task() # record end time time_end = monotonic() # calculate the duration time_duration = time_end - time_start # report the duration print(f'Took {time_duration:.3f} seconds') |
Running the example first records the start time, a number from an internal clock.
Next, the Python function is called, in this case creating a list of 100 million squared integers.
The end time is then recorded, as a number from an internal clock.
The difference between the two recorded times is calculated, providing the function execution duration in seconds.
Finally, the result is reported, truncated to three decimal places (milliseconds).
In this case, we can see that the function took about 6.431 seconds to complete.
Note, the results on your system may vary.
This highlights how we can benchmark a Python function using the time.monotonic() function.
1 |
Took 6.112 seconds |
Next, let’s explore an example of benchmarking a script using the time.monotonic() function.
Example of Benchmarking a Script with time.monotonic()
We can explore how to benchmark a Python script (Python file) using time.monotonic() with a worked example.
In this example, we will update the above example so that it has a main() function and protects the entry point, like a more elaborate Python script.
1 2 3 4 |
# main function for script def main(): # call a function task() |
We will then add benchmarking code around the call to the main() function.
1 2 3 4 5 6 7 8 9 10 11 12 |
# protect the entry point if __name__ == '__main__': # record start time time_start = monotonic() # execute the script main() # record end time time_end = monotonic() # calculate the duration time_duration = time_end - time_start # report the duration print(f'Took {time_duration:.3f} seconds') |
Tying this together, the complete example is listed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
# SuperFastPython.com # example of benchmarking a script with time.monotonic() from time import monotonic # function to benchmark def task(): # create a large list data = [i*i for i in range(100000000)] # main function for script def main(): # call a function task() # protect the entry point if __name__ == '__main__': # record start time time_start = monotonic() # execute the script main() # record end time time_end = monotonic() # calculate the duration time_duration = time_end - time_start # report the duration print(f'Took {time_duration:.3f} seconds') |
Running the example first records the start time, a number from an internal clock.
Next, the main() function is called which executes the core of the Python script. In this case, it calls our task() function and creates a list of 100 million squared integers.
The end time is then recorded, as a number from an internal clock.
The difference between the two recorded times is calculated, providing the function execution duration in seconds.
Finally, the result is reported, truncated to three decimal places (milliseconds).
In this case, we can see that the function took about 6.438 seconds to complete.
Note, the results on your system may vary.
This highlights how we can benchmark a Python script using the time.monotonic() function.
1 |
Took 6.162 seconds |
Confirm time.monotonic() Does Include Sleep
The time.monotonic() function does include time spent blocked or sleeping.
When the program is blocked or sleeping, the clock used by time.monotonic() is not paused.
We can demonstrate this with a worked example.
We can update the example of benchmarking a statement and include a sleep for 2 seconds.
For example:
1 2 3 |
... # sleep for a moment sleep(2) |
This will have an effect on the benchmark time, e.g. it should increase the benchmark time from about 5 seconds to about 7 seconds.
Tying this together, the complete example is listed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# SuperFastPython.com # example of benchmarking a statement and sleep with time.monotonic() from time import monotonic from time import sleep # record start time time_start = monotonic() # execute the statement data = [i*i for i in range(100000000)] # sleep for a moment sleep(2) # record end time time_end = monotonic() # calculate the duration time_duration = time_end - time_start # report the duration print(f'Took {time_duration:.3f} seconds') |
Running the example, we can see that the addition of the sleep() after the target code does have the intended effect.
The time of the benchmark increases from about 5 seconds to about 7 seconds.
This highlights that time spent explicitly sleeping is included in the benchmark time when using time.monotonic().
1 |
Took 7.109 seconds |
Confirm time.monotonic() Is Monotonic and Not Adjustable
The clock used by the time.monotonic() function is not adjustable.
We can confirm this by reporting the details of the “monotonic” function in the time module via the time.get_clock_info() function.
This reports the details of the clock used by a function, like monotonic, such as whether it is adjustable, how it is implemented on the platform, whether it is monotonic, and the resolution on the platform.
The program below reports the details of the clock used by the time.monotonic() function.
1 2 3 4 5 6 7 |
# SuperFastPython.com # details of the clock used by time.monotonic() from time import get_clock_info # get details details = get_clock_info('monotonic') # report details print(details) |
Running the program reports the details of the monotonic clock.
We can see that indeed it is monotonic and that the resolution on the platform is 1e-09.
We can also confirm that it is not adjustable (at least on the platform on which this program was run).
1 |
namespace(implementation='mach_absolute_time()', monotonic=True, adjustable=False, resolution=1e-09) |
Further Reading
This section provides additional resources that you may find helpful.
Books
- Python Benchmarking, Jason Brownlee (my book!)
Also, the following Python books have chapters on benchmarking that may be helpful:
- Python Cookbook, 2013. (sections 9.1, 9.10, 9.22, 13.13, and 14.13)
- High Performance Python, 2020. (chapter 2)
Guides
- 4 Ways to Benchmark Python Code
- 5 Ways to Measure Execution Time in Python
- Python Benchmark Comparison Metrics
Benchmarking APIs
- time — Time access and conversions
- timeit — Measure execution time of small code snippets
- The Python Profilers
References
Takeaways
You now know how to benchmark Python code using the time.monotonic() function.
Did I make a mistake? See a typo?
I’m a simple humble human. Correct me, please!
Do you have any questions?
Ask your questions in the comments below and I will do my best to answer.
Photo by Faris Mohammed on Unsplash
Do you have any questions?