Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to measure Python's asyncio code performance?

I can't use normal tools and technics to measure the performance of a coroutine because the time it takes at await should not be taken in consideration (or it should just consider the overhead of reading from the awaitable but not the IO latency).

So how do measure the time a coroutine takes ? How do I compare 2 implementations and find the more efficent ? What tools do I use ?

like image 442
e-satis Avatar asked Jan 16 '16 11:01

e-satis


People also ask

How is Python code performance measured?

We should measure the performance of blocks of python code in a project by recording the execution time and by finding the amount of memory being used by the block. This will help us to know the size of the system required to run the application and also get an idea of the duration of the run.

Is async IO slow?

Actually, asyncio is much slower due to the high impact of using coroutines. I have no numbers, so this is just a comment, instead of a post, but you can verify this with a simple http echo server written in both styles. Python + high performance async IO do not work together, sadly.

When should I use async IO Python?

asyncio is used as a foundation for multiple Python asynchronous frameworks that provide high-performance network and web-servers, database connection libraries, distributed task queues, etc. asyncio is often a perfect fit for IO-bound and high-level structured network code.


1 Answers

This answer originally contained two different solutions: the first one was based on monkey-patching and the second one does not work for python 3.7 and onward. This new version hopefully presents a better, more robust approach.

First off, standard timing tools such as time can be used to determine the CPU time of a program, which is usually what we're interested in when testing the performance of an asynchronous application. Those measurements can also be performed in python using the time.process_time() function:

import time

real_time = time.time()
cpu_time = time.process_time()

time.sleep(1.)
sum(range(10**6))

real_time = time.time() - real_time
cpu_time = time.process_time() - cpu_time

print(f"CPU time: {cpu_time:.2f} s, Real time: {real_time:.2f} s")

See below the similar output produced by both methods:

$ /usr/bin/time -f "CPU time: %U s, Real time: %e s" python demo.py
CPU time: 0.02 s, Real time: 1.02 s  # python output
CPU time: 0.03 s, Real time: 1.04 s  # `time` output

In an asyncio application, it might happen that some synchronous part of the program ends up performing a blocking call, effectively preventing the event loop from running other tasks. So we might want to record separately the time the event loop spends waiting from the time taken by other IO tasks.

This can be achieved by subclassing the default selector to perform some timing operation and using a custom event loop policy to set everything up. This code snippet provides such a policy along with a context manager for printing different time metrics.

async def main():
    print("~ Correct IO management ~")
    with print_timing():
        await asyncio.sleep(1)
        sum(range(10**6))
    print()

    print("~ Incorrect IO management ~")
    with print_timing():
        time.sleep(0.2)
        await asyncio.sleep(0.8)
        sum(range(10**6))
    print()

asyncio.set_event_loop_policy(TimedEventLoopPolicy())
asyncio.run(main(), debug=True)

Note the difference between those two runs:

~ Correct IO management ~
CPU time:      0.016 s
Select time:   1.001 s
Other IO time: 0.000 s
Real time:     1.017 s

~ Incorrect IO management ~
CPU time:      0.016 s
Select time:   0.800 s
Other IO time: 0.200 s
Real time:     1.017 s

Also notice that the asyncio debug mode can detect those blocking operations:

Executing <Handle <TaskWakeupMethWrapper object at 0x7fd4835864f8>(<Future finis...events.py:396>) created at ~/miniconda/lib/python3.7/asyncio/futures.py:288> took 0.243 seconds
like image 83
Vincent Avatar answered Oct 11 '22 19:10

Vincent