Everyone knows theasyncio
module in python schedules all coroutines in a single thread. That means it helps you code easier, and you can't gain any performance from it.
But what is the performance of python'sasyncio
module? How fast can it run compared to traditionalgevent
and nativeepoll
?
Requirements
pip3installhiredis gevent
An optional packageuvloop can also be install if working on Linux:
pip3installuvloop
Source
The following code uses bothasyncio
andgevent
to simulate a redis server on port 5000, 5001, and 5002.
They can be tested withredis-benchmark
.
content ofecho_bench_gevent.py
:
importsysimportgeventimportgevent.monkeyimporthiredisfromgevent.serverimportStreamServergevent.monkey.patch_all()d={}defprocess(req):# only support get/setcmd=req[0].lower()ifcmd==b'set':d[req[1]]=req[2]returnb"+OK\r\n"elifcmd==b'get':v=d.get(req[1])ifvisNone:returnb'$-1\r\n'else:returnb'$1\r\n1\r\n'else:print(cmd)raiseNotImplementedError()returnb''defhandle(sock,addr):reader=hiredis.Reader()whileTrue:buf=sock.recv(4096)ifnotbuf:returnreader.feed(buf)whileTrue:req=reader.gets()ifnotreq:breaksock.sendall(process(req))return0print('serving on 0.0.0.0:5000')server=StreamServer(('0.0.0.0',5000),handle)server.serve_forever()
content ofecho_bench_asyncio.py
:
importasyncioimporthiredisd={}defprocess(req):cmd=req[0].lower()ifcmd==b'set':d[req[1]]=req[2]returnb"+OK\r\n"elifcmd==b'get':v=d.get(req[1])ifvisNone:returnb'$-1\r\n'else:returnb'$1\r\n1\r\n'elifcmd==b'config':returnb'-ERROR\r\n'else:returnb'-ERROR\r\n'returnb''asyncdefecho_server(reader,writer):hireader=hiredis.Reader()whileTrue:s=awaitreader.read(4096)ifnots:breakhireader.feed(s)whileTrue:req=hireader.gets()ifnotreq:breakres=process(req)writer.write(res)awaitwriter.drain()return0asyncdefmain():server=awaitasyncio.start_server(echo_server,'0.0.0.0',5001)print('serving on {}'.format(server.sockets[0].getsockname()))awaitserver.serve_forever()return0asyncio.run(main())
content ofecho_bench_asyncio_uvloop.py
:
importasyncioimporthiredisd={}defprocess(req):cmd=req[0].lower()ifcmd==b'set':d[req[1]]=req[2]returnb"+OK\r\n"elifcmd==b'get':v=d.get(req[1])ifvisNone:returnb'$-1\r\n'else:returnb'$1\r\n1\r\n'elifcmd==b'config':returnb'-ERROR\r\n'else:returnb'-ERROR\r\n'returnb''asyncdefecho_server(reader,writer):hireader=hiredis.Reader()whileTrue:s=awaitreader.read(4096)ifnots:breakhireader.feed(s)whileTrue:req=hireader.gets()ifnotreq:breakres=process(req)writer.write(res)awaitwriter.drain()return0asyncdefmain():server=awaitasyncio.start_server(echo_server,'0.0.0.0',5002)print('serving on {}'.format(server.sockets[0].getsockname()))awaitserver.serve_forever()return0try:importuvloopuvloop.install()print('uvloop is enabled')exceptImportError:print('uvloop is not available')asyncio.run(main())
Start servers
python3 echo_bench_gevent.py# will listen on port 5000python3 echo_bench_asyncio.py# will listen on port 5001python3 echo_bench_asyncio_uvloop.py# will listen on port 5002
Test
redis-benchmark-p 5000-t get-n 100000-r 100000000redis-benchmark-p 5001-t get-n 100000-r 100000000redis-benchmark-p 5002-t get-n 100000-r 100000000
Result
Mode | Python 3.9 | Python 3.11 |
---|---|---|
gevent | 34281.80 requests / second | 32258.07 requests / second |
asyncio | 40144.52 requests / second | 51652.89 requests / second |
asyncio + uvloop | 64102.57 requests / second | 66577.90 requests / second |
Native epoll
Redis is implemented withepoll
in C, and we can test redis directly:
redis-benchmark-p 6379-t get-n 100000-r 100000000
Output:
75244.55 requests per second
Conclusion
asyncio
is 50% faster thangevent
in Python 3.11asyncio
can run twice as fast asgevent
withuvloop
.asyncio
can go up to 68% of a native epoll program.asyncio
can go up to 88% of a native epoll program withuvloop
.
Top comments(2)

Thank you for the nice write-up. Using asyncio + uvloop + transports/protocols is even faster and on par with the benchmark against native Redis on my machine:
importasyncioimporthiredisd={}defprocess(req):cmd=req[0].lower()ifcmd==b'set':d[req[1]]=req[2]returnb"+OK\r\n"elifcmd==b'get':v=d.get(req[1])ifvisNone:returnb'$-1\r\n'else:returnb'$1\r\n1\r\n'elifcmd==b'config':returnb'-ERROR\r\n'else:returnb'-ERROR\r\n'returnb''classRedisServerProtocol(asyncio.Protocol):defconnection_made(self,transport):self.transport=transportself.hireader=hiredis.Reader()defdata_received(self,data):self.hireader.feed(data)whileTrue:req=self.hireader.gets()ifnotreq:breakres=process(req)self.transport.write(res)asyncdefmain():loop=asyncio.get_running_loop()server=awaitloop.create_server(lambda:RedisServerProtocol(),'0.0.0.0',5003)print('serving on {}'.format(server.sockets[0].getsockname()))asyncwithserver:awaitserver.serve_forever()try:importuvloopuvloop.install()exceptImportError:print('uvloop is not available')asyncio.run(main())
Above code gives me:
redis-benchmark -p 5003 -t get -n 100000 -r 100000000...138696.25 requests per second
compared to
redis-benchmark -p 6379 -t get -n 100000 -r 100000000...134589.50 requests per second
For further actions, you may consider blocking this person and/orreporting abuse