Immediately stop using time() to test the performance of functions in python

If you're used to measuring the performance of tasks in Python using the time.time() function instead of time.monotonic() or time.perf_counter() , and wondering why, then this article is for you.

First we import the module:

improt time

Let's start with a simple example

For the sake of simplicity, suppose your task is to count the number of occurrences of a certain value in a two-dimensional array, but you are not satisfied with just doing this, you also want to measure the time it takes to do so, the code implementation is as follows:

def count_value(array, value):
   c = 0
   t_start = time.time()
   for i in array:
       for j in i:
           if j == value:
               c += 1
   t_end = time.time()

   return c, t_end-t_start

Now let's use this function on some arrays:

a_1 = [[1,2],[3,3]]
a_2 = [[33]]
a_3 = [[1 for _ in range(10)] for _ in range(100)]
a_4 = [[i*j for j in range(1000)] for i in range(1000)]

print(count_value(a_1, 3))
print(count_value(a_2, 10))
print(count_value(a_3, 1))
print(count_value(a_4, 44))

output:

(2, 0.0)
(0, 0.0)
(1000, 0.0)
(6, 0.03124070167541504)

Now before discussing the actual problem, I suggest using decorators to measure the performance of functions, but this is beyond the scope of this article, and I will write a separate article later. So let's talk about the above issues. On 3 of the 4 arrays, we see that the time measured for the search is 0, which means the search is instant, it is not, the problem here is that the time.time() function uses the system clock, which has several question:

  • The tick rate of this clock is not small enough, if the task is executed between two "ticks" of the clock, we will not be able to measure the actual length of the task, because it will be shorter than the resolution of the clock

  • The system clock can be modified by external factors such as updates, clock calibration or leap seconds.

To solve these problems, the time library has 2 functions: monotonic() and perf_counter (or 4 functions if we also consider nanosecond alternatives: monotonic_ns() and perf_counter_ns()).

It is important to know that these functions solve both problems: the monotonic() function is based on a linear clock and cannot be modified from the outside, while the perf_counter() function has a higher tick rate.

Show tick rate difference

Let's write a function to display the different tick rates for all three functions: time(), monotonic(), and perf_counter. I'll use perf_counter_ns() to measure elapsed time.

def tick_rate(f):
   tick = 0
   t_start = time.perf_counter_ns()
   last_t = f()
   for i in range(10_000_000):
       t = f()
       if t != last_t:
           tick += 1
       last_t = t
   t_end = time.perf_counter_ns()

   return tick, (t_end-t_start)/(10**9) #divided by 10^9 to convert ns to s

print(tick_rate(time.time))
print(tick_rate(time.monotonic))
print(tick_rate(time.perf_counter))

output:

(59, 0.9173757)
(50, 0.7825792)
(10000000, 1.615493)

Now we can see a huge difference in the ability to measure small tasks using the perf_counter() function, in fact, we cannot measure the maximum tick rate of the clock used by perf_counter(), but it is still large. The monotonic() function can be used for longer task.

Same example using perf_counter() Let's use the same example as before, but use perf_counter() instead of time():

def count_value(array, value):
   c = 0
   t_start = time.perf_counter()
   for i in array:
       for j in i:
           if j == value:
               c += 1
   t_end = time.perf_counter()

   return c, t_end-t_start

a_1 = [[1,2],[3,3]]
a_2 = [[33]]
a_3 = [[1 for _ in range(10)] for _ in range(100)]
a_4 = [[i*j for j in range(1000)] for i in range(1000)]

print(count_value(a_1, 3))
print(count_value(a_2, 10))
print(count_value(a_3, 1))
print(count_value(a_4, 44))

output:

(2, 4.7999997150327545e-06)
(0, 1.4000002011016477e-06)
(1000, 7.83999998930085e-05)
(6, 0.036078900000120484)

As we saw above, we can actually measure the time spent by using the correct function.

In my next article, I'll discuss decorators and why they're great to use when measuring the performance of functions. If you've read to the end of this article, please consider subscribing to my newsletter, it's free and keeps me motivated!

Original text: https://www.raaicode.com/using-time-monotonic-and-perf_counter-to-measure-time-in-python/

Guess you like

Origin blog.csdn.net/y1282037271/article/details/129200383