I'm trying to get familiar with asyncio, so I've decided to write a database client. However, performance exactly matches synchronous code. I'm sure this is my misunderstanding of a concept. Could someone explain what am I doing wriong?
Please see example of code below:
class Connection:
def __init__(self, reader, writer, loop):
self.futures = deque()
# ...
self.reader_task = asyncio.async(self.recv_data(), loop=self.loop)
@asyncio.coroutine
def recv_data(self):
while 1:
try:
response = yield from self.reader.readexactly(4)
size, = struct.unpack('I', response)
response = yield from self.reader.readexactly(size)
# ...
future = self.futures.popleft()
if not future.cancelled():
future.set_result(response)
except Exception:
break
def send_data(self, data):
future = asyncio.Future(loop=self.loop)
self.futures.append(future)
self.writer.write(data)
return future
loop = asyncio.get_event_loop()
@asyncio.coroutine
def benchmark():
connection = yield from create_connection(loop=loop, ...)
for i in range(10000):
yield from connection.send_data(...)
s = time.monotonic()
loop.run_until_complete(benchmark())
e = time.monotonic()
print('Requests per second:', int(10000 / (e - s)))
Thanks in advance.
Standard library asyncio is definitely slower than most multi-threaded frameworks, because asyncio executes a lot of Python for each event. Generally frameworks are faster the more that they're implemented in C or another compiled language.
One of the cool advantages of asyncio is that it scales far better than threading . Each task takes far fewer resources and less time to create than a thread, so creating and running more of them works well.
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.
Asyncio vs threading: Async runs one block of code at a time while threading just one line of code at a time. With async, we have better control of when the execution is given to other block of code but we have to release the execution ourselves.
You've made a mistake in the way you're calling send_data
. Right now, you've got this:
@asyncio.coroutine
def benchmark():
connection = yield from create_connection(loop=loop, ...)
for i in range(10000):
yield from connection.send_data(...)
By using yield from
inside the for loop, you're waiting for the future
you're returning from send_data
to yield a result before moving on to the next call. This makes your program basically synchronous. You want to make all your calls to send_data
, and then wait for results:
@asyncio.coroutine
def benchmark():
connection = yield from create_connection(loop=loop, ...)
yield from asyncio.wait([connection.send_data(..) for _ in range(10000)])
The python asyncio module is single threaded:
This module provides infrastructure for writing single-threaded concurrent code using coroutines, multiplexing I/O access over sockets and other resources, running network clients and servers, and other related primitives.
This question has an explanation of why asyncio can be slower than threading, but in short: asyncio uses a single thread to execute your code, so even if you have multiple coroutines, they all execute serially. A thread pool is used to execute some callbacks and I/O. Because of the GIL, threading also executes user code serially, though I/O operations can be run synchronously.
The reason using asyncio doesn't give you an improvement over serially executed code, is because the event loop is only running one coroutine at a time.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With