How to Use Python Timer Functions for Performance Measurement

Python timer functions allow you to monitor the execution time of a Python script.

They are a useful tool for improving performance of Python applications.

In this tutorial, you learn everything you need to know about timer functions in Python.

Table of Contents

You can skip to a specific section of this Python timer tutorial using the table of contents below:

Introduction to Python Timers

The Python time module can be used to measure the performance of a pure Python program. This is vital as one of the significant limitations of Python programmes is they are a bit slower compard to some programming languages. In these instances, we can use python timers to identify the performance impact of the code and make the necessary modifications.

Python Timer Functions

Here are some predefined functions in built-in time module.

  • perf_counter()
  • monotonic()
  • process_time()
  • time()

With Python 3.7, new time functions like treadtime() and nanosecond versions of all the above functions were introduced. For example, perfcounter() function gained a nanosecond counterpart called perfcounterns(). All nanosecond functions come with _ns suffix.

Basic Usage of Timer Function

perfcounter() and perfcounter_ns() Functions

In this section, we will use perfcounter() and perfcounter_ns() to monitor the execution time of a simple program.

In the following example, we measure the time elapsed to run the print() function.

import time
 
start_time = time.perf_counter()
print("Hello World")
end_time = time.perf_counter()
 
print(f"Start Time : {start_time}")
print(f"End Time : {end_time}")
print(f"Execution Time : {end_time - start_time:0.6f}" )
 
print()
start_time = time.perf_counter_ns()
print("Hello World")
end_time = time.perf_counter_ns()
 
print(f"Start Time : {start_time}")
print(f"End Time : {end_time}")
print(f"Execution Time in Nanoseconds : {end_time - start_time}" )

RESULT

In the next script, we use timers as a function as well as a performance metric. Using the perf_counter() function, we check how long it takes to find the network speed using the “speedtest” module.

import speedtest
import datetime
import time
 
def check_speed_format(): 
    test = speedtest.Speedtest() 
    number_of_times = 0
    while number_of_times < 2:
        # Get Code Block Start Time
        start_time = time.perf_counter()
 
        time_now = datetime.datetime.now().strftime("%H:%M:%S")
        downspeed = round((round(test.download()) / 1048576), 2)
        upspeed = round((round(test.upload()) / 1048576), 2)
        print(f"time: {time_now}, downspeed: {downspeed} Mb/s, upspeed: {upspeed} Mb/s")
 
        # Get Code Block End Time
        end_time = time.perf_counter()
 
        # Print the Execution Time
        print(f"Start Time : {start_time}")
        print(f"End Time : {end_time}")
        print(f"Total Execution Time : {end_time - start_time:0.4f} \n" )
 
        # Using sleep() to pause the program
        time.sleep(60)
        number_of_times += 1
 
if __name__ == "__main__":
    check_speed_format()

RESULT

From the result, we can determine that the upload and download speeds of the network take around 20 seconds. Using f-strings we format the total time to a decimal number with 4 decimal points.

As the perfcounter() returns the value of a performance counter in fractional seconds this function is the best choice to deal with shorter periods because it is a clock with the highest available resolution. Using perfcounter functions will lead to a more precise output value.

monotonic() Function

The monotonic() method in the time module is used to get the time value of a monotonic clock. A monotonic clock is a clock, which only runs in a single direction, without the ability to rewind the clock. This method returns a float value, which represents the value of a monotonic clock in fractional seconds. The monotonic method is best suited for capturing time frames larger than a second.

Example

import time
 
# Get the value of monotonic clock
# at the start time for the process.
start_time =  time.monotonic()          
 
print("Monotonic clock value at start =  ", start_time)
 
user_input= input("Enter a Number :  ")
print(str(user_input))
 
# Get the value of monotonic clock
# at the end time for the process.
end_time = time.monotonic()       
 
print("Monotonic clock value at end = ", end_time)
print("Time Elapsed = " , (end_time - start_time))

RESULT

process_time() Function

The processtime function only calculates the system and the CPU time during the process. This will not include sleep time unlike perfcounter(), which calculates time including the sleep time. This function can be used whenever there is a need to calculate the time taken for the CPU to complete a particular process. The following examples demonstrates how processtime() differs from perfcounter()

process_time()

import time
 
start_time =  time.process_time()       
 
print("Process time at start =  ", start_time)
 
time.sleep(10)
 
x = 0
for x in range(1000000):
    x = x + 1
print(f"The Loop ran {str(x)} times")
 
end_time = time.process_time()       
 
print("Process time at end = ", end_time)
print("Time Elapsed = " , (end_time - start_time))

RESULT

perf_counter()

import time
 
start_time =  time.perf_counter()       
 
print("Perf_Counter time at start =  ", start_time)
 
time.sleep(10)
 
x = 0
for x in range(1000000):
    x = x + 1
print(f"The Loop ran {str(x)} times")
 
end_time = time.perf_counter()       
 
print("Perf_Counter time at end = ", end_time)
print("Time Elapsed = " , (end_time - start_time))

RESULT

As you can see from the results of the above programs, the processtime() method only calculates the time elapsed to run the program logic ignoring the sleep time. The perfcounter() method captures the total time including the sleep time which in this instance is 10 seconds.

time() Function

The time() function in the time module is used to get the time in seconds since epoch. The epoch is the point where time starts, and it is platform dependent. On Windows and Unix systems, the epoch is 1/1/1970, 00:00:00. Following example demonstrates how to get the epoch time.

Getting the system epoch time example.

import time
 
# Get the system epoch
temp = time.gmtime(0)
epoch = time.asctime(temp)
print("epoch is : ", epoch)
 
# Get the time in seconds since the epoch
time_now = time.time()
print("Time since the epoch in seconds : ", time_now)

RESULT

Python Timer Class

Classes are a foundational element in object-oriented programming. A class is a basic template which can be used to create other objects based on that class. A class consists of a collection of properties and behaviours. In python performance monitoring a class can be used to keep track of a particular state of an object.

Benefits of Using a Class

  • Flexibility - The reusability of code increases as the class can be called in multiple instances with a code block.
  • Readability - The code becomes more readable and easier to understand.
  • Consistency - When properties and behaviours are changed into specific attributes and methods code becomes more consistent.

Creating a Python Timer Class

Following code-block demonstrates how a python class can be created to keep track of when a timer starts and the elapsed time.

import time
 
class Timer:
 
    def __init__(self):
        self._start_time = None        
                                                         
    def start(self):
        # Check if the timer is running
        # If not stat the timer
        if self._start_time is None:                            
            self._start_time = time.perf_counter()              
 
    def stop(self): 
        # Stop the timer then report the elapsed time
        if self._start_time is not None:                                            
            elapsed_time = time.perf_counter() - self._start_time 
            self._start_time = None 
            # Using f-string to format time to four decimals
            print(f"Elapsed time : {elapsed_time:0.4f} seconds")
 
if __name__ == "__main__":
    # Create a new object using Timer Class
    new_timer = Timer()
    new_timer.start()
    time.sleep(10)
    new_timer.stop()

RESULT

A new instance of the Timer class is created called “new_timer” then we call the start function and inform the program to wait for 10 seconds using “time.sleep(10)” and finally call the stop function to stop the time calculation and return the elapsed time.

Because the start() and stop() functions can be called upon multiple times this can lead to unintended consequences. To mitigate this, we modify the code to raise an error if either of the two functions is called multiple times as shown below.

import time
 
class TimerError(Exception):
    """An exception used to report errors in use of Timer class"""
 
class Timer:
 
    def __init__(self):
        self._start_time = None        
                                                         
    def start(self):
        if self._start_time is not None:                            
            raise TimerError(f"Timer is running. Use .stop() to stop it")
        elif self._start_time is None:
            self._start_time = time.perf_counter()                              
 
    def stop(self): 
        # Check if start_time is assigned
        if self._start_time is None:
            raise TimeoutError(f"Timer is not running. Use .start() to start it")
        # Stop the timer then report the elapsed time
        elif self._start_time is not None:                                          
            elapsed_time = time.perf_counter() - self._start_time 
            self._start_time = None 
            # Using f-string to format time to four decimals
            print(f"Elapsed time : {elapsed_time:0.4f} seconds")

We test if the TimerError is working by deliberately making incorrect calls to start() and stop() functions in the “main” execution block.

Testing start() Error

if __name__ == "__main__":
    # Create a new object using Timer Class
    new_timer = Timer()
    new_timer.start()
    new_timer.start()
    time.sleep(10)
    new_timer.stop()

RESULT

Testing stop() Error

if __name__ == "__main__":
    # Create a new object using Timer Class
    new_timer = Timer()
    new_timer.stop()

RESULT

We create a new class called “TimerError”, that will be used to capture any errors in start() and stop() functions. As you can see from the above tests both if the start() or the stop() functions are called incorrectly it will generate a “TimerError” and inform the user.

Improving the Timer Class

Timer Class variable string output

To make the Timer class more flexible first we will provide the user with the option to determine how the output will be shown. In the previous code-block for Timer Class, we hardcoded the output as follows.

print(f"Elapsed time : {elapsed_time:0.4f} seconds")

This wording makes sense if the user wants to find the elapsed time as the output but cannot be used in every scenario. Hence, we can provide the user with the option to set a custom output text so the Timer Class will be more flexible in its use. Below is the improved Timer Class.

import time
import wget
from wget import bar_adaptive
 
class TimerError(Exception):
    """An exception used to report errors in use of Timer class"""
 
class Timer:
 
    def __init__(self, text="Elapsed time: {:0.4f} seconds"):
        self._start_time = None 
        self.text = text       
                                                         
    def start(self):
        if self._start_time is not None:                            
            raise TimerError(f"Timer is running. Use .stop() to stop it")
        elif self._start_time is None:
            self._start_time = time.perf_counter()                              
 
    def stop(self): 
        if self._start_time is None:
            raise TimeoutError(f"Timer is not running. Use .start() to start it")
        # Stop the timer then report the elapsed time
        elif self._start_time is not None:                                          
            elapsed_time = time.perf_counter() - self._start_time 
            self._start_time = None 
            # Use the User provided string as the output string
            print("")
            print(self.text.format(elapsed_time))
 
if __name__ == "__main__":
    # Create a new object using Timer Class
    new_timer = Timer(text="Total Download time is {:0.2f} seconds")
 
    # Find the time required to download the python installer
    new_timer.start()
    download_url = "https://www.python.org/ftp/python/3.9.0/python-3.9.0-amd64.exe"
    wget.download(download_url,bar=bar_adaptive)
    new_timer.stop()

RESULT

In the above example, we provide the end-user with the choice to define the output string enabling the Timer Class to be used in any situation. In this example, we check for the time it takes to download the python installer. As the output string the user will define “Total Download time is {:0.2f} seconds” to adapt the Timer Class object to this new use case.

Timer Class with a return value

If we want to save the elapsed time as a value in the code instead of displaying it, we can modify the stop() function in the Timer Class just by adding a return statement as follows:

    def stop(self): 
    
        if self._start_time is None:
            raise TimeoutError(f"Timer is not running. Use .start() to start it")
        # Stop the timer then report the elapsed time
        elif self._start_time is not None:                                          
            elapsed_time = time.perf_counter() - self._start_time 
            self._start_time = None 
            # Use the User provided string as the output string
            print("")
            print(self.text.format(elapsed_time))
            return elapsed_time

Then we execute the programme.

if __name__ == "__main__":

    new_timer = Timer(text="Total Download time is {:0.2f} seconds")
 
    # Find the time required to download the python installer
    new_timer.start()
    download_url = "https://www.python.org/ftp/python/3.9.0/python-3.9.0-amd64.exe"
    wget.download(download_url, bar=bar_adaptive)
    total_time = new_timer.stop()
    
    print(str(total_time))

RESULT

As shown above, we return the elapsed time in the stop() function. When calling the stop() function, we assign the function to a variable called total_time to capture the return value.

Timer Class to store multiple measurements

To use more than a single timer in a particular code and store the performance metrics obtained by the corresponding timer in an orderly manner, we will implement a dictionary in the Timer class to capture each separate timer object.

import time
 
class TimerError(Exception):
    """An exception used to report errors in use of Timer class"""
 
class Timer:
 
    timers = dict()
 
    def __init__(self, text="Elapsed time: {:0.4f} seconds", name=None):
        self._start_time = None 
        self.name = name
        self.text = text
 
        # Add a new timer to the dictionary
        if name:
            self.timers.setdefault(name, 0)
 
                                                         
    def start(self):
        if self._start_time is not None:                            
            raise TimerError(f"Timer is running. Use .stop() to stop it")
        elif self._start_time is None:
            self._start_time = time.perf_counter()                              
 
    def stop(self): 
        if self._start_time is None:
            raise TimeoutError(f"Timer is not running. Use .start() to start it")
        # Stop the timer then report the elapsed time
        elif self._start_time is not None:                                          
            elapsed_time = time.perf_counter() - self._start_time 
            self._start_time = None 
            # Use the User provided string as the output string
            print("")
            print(self.text.format(elapsed_time))
 
            #Add times measured by the same timers
            if self.name is not None:
                self.timers[self.name] += elapsed_time
 
            return elapsed_time
 
if __name__ == "__main__":
    # Create a new object using Timer Class
    new_timer_one = Timer(name="test_timer_01")
    new_timer_one.start()
    time.sleep(3)
    new_timer_one.stop()
    new_timer_one.start()
    time.sleep(4)
    new_timer_one.stop()
 
    new_timer_two = Timer(name="test_timer_02")
    new_timer_two.start()
    time.sleep(5)
    new_timer_two.stop()
    new_timer_two.start()
    time.sleep(10)
    new_timer_two.stop()
 
    print(Timer.timers)

RESULT

As you can see from the results, each separate timer has been captured by the dictionary. Furthermore, the total elapsed time for each timer is calculated and presented as an item. To achieve this, we use the “setdefault()” method to add a new python timer item to the dictionary. From this method, we make sure that the timer value is set to zero only if a new timer is introduced.

The next section will demonstrate how this improved Timer Class can be used to obtain the performance metrics of a program. In the code-block shown below, we define two functions to count prime numbers then use the Timer Class to calculate the elapsed time.

from timer import Timer
 
def count_primes_one(start, end):
    count= 0
    for i in range(start, end+1):
        if i==2:
            count+=1
        else:
            for j in range(2,i):
                if i%j==0:
                    break
                if j== i-1:
                    count+=1                
    return count
 
def count_primes_two(start, end):
    count= 0
    for i in range(start, end+1):
        if i==2:
            count+=1
        else:
            for j in range(2,i):
                if i%j==0:
                    break
                if j>= i/j:
                    count+=1
                    break                
    return count
 
if __name__ == "__main__":
    # Create a new object using Timer Class
    new_timer_one = Timer(name="test_timer_01")
    new_timer_one.start()
    print("")
    print(count_primes_one(1, 1000))    
    new_timer_one.stop()
 
    new_timer_two = Timer(name="test_timer_02")
    new_timer_two.start()
    print("")
    print(count_primes_two(1, 2000))
    new_timer_two.stop()

RESULT

As indicated by the result, we successfully used the Timer Class to check the performance of the functions “countprimeone” and “countprimetwo” and how long it took each function to calculate the prime numbers in the given range.

Final Thoughts

In this article, we understood how the performance of programs can be measured using python's built-in functions and how to create a custom python class to measure performance metrics. Identifying and quantifying the performance of a program is crucial in a production environment, as this can help mitigate performance-related issues as well as to reduce the resource usage of a particular program.

If you enjoyed this article, be sure to join my Developer Monthly newsletter, where I send out the latest news from the world of Python and JavaScript:
Written on October 15th, 2020