fastapi+hypercorn/gunicorn/flask 性能测试记录

一直听闻 fastapi 速度比 flask 快,作为异步框架比flask要快,甚至能比肩 node.js 与 go ,所以想测试一下,故有以上测试记录。

从结果来看 fastapi 确实要比 flask 快,但是也有一些让人困惑的点,部分相关测试的代码将在文末提供。

  1. 异步代码的提升并不是很高,httpx 的异步代码反而比同步代码要慢不少
  2. 异步代码在多个 worker 的情况下,除了 sleep 测试,均比不上同步代码
  3. 使用多个 worker 提升并不明显,在 httpx 连接测试中,使用 2 个worker 可以提升 1 倍,但再提升到 4 个时效率增加不明显
  4. hypercorn 使用 uvloop 工作类时,httpx 连接测试提升明显,但是 sql 和 sleep 测试反而变慢了不少
  5. 还有一个重要的发现,如果在同步代码的函数前加 asyncasync def xxx(): ,反而会使响应变的超慢(服务器会只能处理完一个请求,再开始处理下一个请求),所以是同步代码的路由函数不应加 async 标志。【注:这个结论测试数据并未在上图中显示出,是当前补充的】

部分测试代码如下:

SQL 测试

同步代码

@app.get("/sql")
def sql_get(*, ses: Session = Depends(get_session), req: Request):
    t1 = time.time()
    statement = select(func.count()).select_from(Test).where(Test.cname.contains('XX'))
    # statement = select(Test).where(Test.id.startswith('a')).limit(1)
    result = ses.exec(statement)
    return [result.all(), time.time() - t1, str(req.url)]

异步代码

@app.get("/sql")
async def sql_get(*, ses: AsyncSession = Depends(get_session), req: Request):
    t1 = time.time()
    statement = select(func.count()).select_from(Test).where(Test.cname.contains('XX'))
    # statement = select(Test).where(Test.id.startswith('a')).limit(1)
    result = await ses.exec(statement)
    return [result.all(), time.time() - t1, str(req.url)]

httpx 测试

同步代码

@app.get("/http")
def http_get(*, client: httpx.Client = Depends(get_httpx_client), req: Request):
    t1 = time.time()
    response = client.get('https://example.com')
    return [response.text, time.time() - t1, str(req.url)]

异步代码

@app.get("/http")
async def http_get(*, client: httpx.AsyncClient = Depends(get_httpx_client), req: Request):
    t1 = time.time()
    response = await client.get('https://example.com')
    return [response.text, time.time() - t1, str(req.url)]

sleep 测试

同步代码

@app.get("/time")
def time_get(*, req: Request):
    t1 = time.time()
    time.sleep(2)
    return [time.time() - t1, str(req.url)]

异步代码

@app.get("/time")
async def time_get(*, req: Request):
    t1 = time.time()
    await asyncio.sleep(2)
    return [time.time() - t1, str(req.url)]
2 个赞

感谢大佬分享

关于1,或许应该用 aiohttp 测异步,可能和我以前的一个贴以及 CatFaceWest 佬给的回答 python问题:asyncio比ThreadPool更慢如何解释?附deepl-pro实测 - #21,来自 CatFaceWest 有些关系

sqlalchemy用的哪个版本的 sqla的异步是基于greenlet的 估计和python自己的async loop循环切换不太一样。所以在使用原生async sleep会比sync sleep快些。

httpx的不清楚。

uvloop+httptools可以更快些。事件循环+解析数据包

同步函数前加async就走了事件循环,因为你函数里还是同步内容,所以就阻塞了事件循环切换。
而不加async则fastapi走的anyio的线程池执行路由函数。

1 个赞

测试了在单个 worker 下,同步 httpx 与异步 aiohttp 的比较,aiohttp 确实比 httpx 更快,快了 16.2% 。

@app.get("/http2")
async def http2_get(*, client: aiohttp.ClientSession = Depends(get_aiohttp_client), req: Request):
    async with client.get('https://connect.rom.miui.com/generate_204') as resp:
        data = await resp.text()
    return [data, str(req.url)]

说实话才16%我有点失望 :grinning:,异步的优越性可能要在大量并发下才能显示出来,服务器端不太清楚,客户端并发请求,异步aiohttp 和同步httpx、requests不可同日而语

我重新使用了 ab 压测命令进行测试,ab -n 100 -c 50 http://127.0.0.1:8000/http 总共发出100个请求,并发50,发现 异步 aiohttp 确实是对异步 httpx 的有碾压性超越,速度是其的 12 倍。另外,还发现异步 httpx 速度 比同步 httpx 还慢了 40%,感觉 httpx 的异步实现问题确实很大

httpx在解决了

你都用Python了,还追求啥性能。

1 个赞

果然是库自身的问题,issue 中的测试结果显示 httpx 也是比 aiohttp 速度差了10x倍