基准Asyncio vs Gevent vs本地Epoll
#python #asyncio

每个人都知道python中的asyncio模块在一个线程中计划所有coroutines。这意味着它可以帮助您更轻松地编码,并且无法从中获得任何性能。

但是Python的asyncio模块的性能是什么?与传统的gevent和本地epoll相比,它可以运行多快?

要求

pip3 install hiredis gevent

如果在Linux上工作,也可以安装一个可选的uvloop

pip3 install uvloop

资源

以下代码同时使用asynciogevent模拟端口5000、5001和5002上的redis服务器。

可以用redis-benchmark进行测试。

echo_bench_gevent.py的内容:

import sys
import gevent
import gevent.monkey
import hiredis

from gevent.server import StreamServer
gevent.monkey.patch_all()

d = {}

def process(req):
    # only support get/set
    cmd = req[0].lower()
    if cmd == b'set':
        d[req[1]] = req[2]
        return b"+OK\r\n"
    elif cmd == b'get':
        v = d.get(req[1])
        if v is None:
            return b'$-1\r\n'
        else:
            return b'$1\r\n1\r\n'
    else:
        print(cmd)
        raise NotImplementedError()
    return b''

def handle(sock, addr):
    reader = hiredis.Reader()
    while True:
        buf = sock.recv(4096)
        if not buf:
            return
        reader.feed(buf)
        while True:
            req = reader.gets()
            if not req:
                break
            sock.sendall(process(req))
    return 0

print('serving on 0.0.0.0:5000')
server = StreamServer(('0.0.0.0', 5000), handle)
server.serve_forever()

echo_bench_asyncio.py的内容:

import asyncio
import hiredis

d = {}

def process(req):
    cmd = req[0].lower()
    if cmd == b'set':
        d[req[1]] = req[2]
        return b"+OK\r\n"
    elif cmd == b'get':
        v = d.get(req[1])
        if v is None:
            return b'$-1\r\n'
        else:
            return b'$1\r\n1\r\n'
    elif cmd == b'config':
        return b'-ERROR\r\n'
    else:
        return b'-ERROR\r\n'
    return b''

async def echo_server(reader, writer):
    hireader = hiredis.Reader()
    while True:
        s = await reader.read(4096)
        if not s:
            break
        hireader.feed(s)
        while True:
            req = hireader.gets()
            if not req:
                break
            res = process(req)
            writer.write(res)
            await writer.drain()
    return 0

async def main():
    server = await asyncio.start_server(echo_server, '0.0.0.0', 5001)
    print('serving on {}'.format(server.sockets[0].getsockname()))
    await server.serve_forever()
    return 0

asyncio.run(main())

echo_bench_asyncio_uvloop.py的内容:

import asyncio
import hiredis

d = {}

def process(req):
    cmd = req[0].lower()
    if cmd == b'set':
        d[req[1]] = req[2]
        return b"+OK\r\n"
    elif cmd == b'get':
        v = d.get(req[1])
        if v is None:
            return b'$-1\r\n'
        else:
            return b'$1\r\n1\r\n'
    elif cmd == b'config':
        return b'-ERROR\r\n'
    else:
        return b'-ERROR\r\n'
    return b''

async def echo_server(reader, writer):
    hireader = hiredis.Reader()
    while True:
        s = await reader.read(4096)
        if not s:
            break
        hireader.feed(s)
        while True:
            req = hireader.gets()
            if not req:
                break
            res = process(req)
            writer.write(res)
            await writer.drain()
    return 0

async def main():
    server = await asyncio.start_server(echo_server, '0.0.0.0', 5002)
    print('serving on {}'.format(server.sockets[0].getsockname()))
    await server.serve_forever()
    return 0

try:
    import uvloop
    uvloop.install()
    print('uvloop is enabled')
except ImportError:
    print('uvloop is not available')

asyncio.run(main())

启动服务器

python3 echo_bench_gevent.py          # will listen on port 5000
python3 echo_bench_asyncio.py         # will listen on port 5001
python3 echo_bench_asyncio_uvloop.py  # will listen on port 5002

测试

redis-benchmark -p 5000 -t get -n 100000 -r 100000000
redis-benchmark -p 5001 -t get -n 100000 -r 100000000
redis-benchmark -p 5002 -t get -n 100000 -r 100000000

结果

模式 python 3.9 python 3.11
gevent 34281.80请求 /秒< / td> 32258.07请求 /秒< / td>
asyncio 40144.52请求 /秒< / td> 51652.89请求 /第二个< / td>
asyncio + uvloop 64102.57请求 /第二个< / td> 66577.90请求 /秒< / td>

本地epoll

redis是用C中的epoll实现的,我们可以直接测试redis:

redis-benchmark -p 6379 -t get -n 100000 -r 100000000

输出:

75244.55 requests per second

结论

  • asyncio比python 3.11
  • 中的gevent快50%
  • asyncio可以使用uvloopgevent速度两倍。
  • asyncio最高可达本机Epoll程序的68%。
  • asyncio可以使用uvloop的本机Epoll程序的88%。