不止一次的听过,有个FastAPI框架,性能碾压Flask,直追Golang,不过一直没有测试过,今天闲着没事测试一下看看结果。不知道是哪里出了问题,结果大跌眼镜。
测试之前
为了偷懒,自然想先从网上找找前人的测试代码以作为参照。百度前几名关于FastAPI和Flask性能测试又带了代码的有下面几个:
- FastAPI、Flask、Golang性能测试
- Flask、Django、Tornado、FastAPI 之 Python Web 并发测试
- flask,tornado,fastapi 压测比较(web框架)
有点疑惑
简单看了一下,没明白,为什么都用了uvicorn启动FastAPI,却只用Flask自带的启动方式,为什么不用其他WSGI服务器?
我觉得这样应该是有问题的,且不说本来二者都不是同一层次的框架(FastAPI是基于Starlette的,这才是应该和Flask对比的框架),就算对比,也应该用差不多的启动方式吧?
uvicorn是个第三方ASGI服务器,Flask应该用一个第三方WSGI服务器来启动才正常吧?感觉用它自带的WSGI服务器比可能不太公平。
我本来想用gunicorn来启动Flask进行对比的,结果发现不兼容Windows,所以换了个waitress,差不多的WSGI框架。
开始测试
- 环境:
Win10 Python3.8.9 各依赖库全是最新版
网上都是用AB测试的,我电脑没装Apache,就用了另一个测试工具siege。测试方式为不限连接数,测试10秒,命令如下:
./siege.exe -b -t10s http://127.0.0.1:5000/
测试代码和之前搜到的一样,用二者官网的例子,输出HelloWorld,略作修改,把启动代码写进文件内,就不用使用命令行启动了。
Flask
from flask import Flask from waitress import serve app = Flask(__name__) @app.route('/') def index(): return {'message': 'hello world'} if __name__ == '__main__': app.run(host='0.0.0.0') # serve(app, host='0.0.0.0', port=5000)
FastAPI
from fastapi import FastAPI import uvicorn app = FastAPI() @app.get("/") async def read_root(): return {"Hello": "World"} if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=5000)
测试结果
鉴于网上的文章在那摆着,所以我也测试了一下使用Flask自带启动方式的结果。
除此之外,还测试了FastAPI使用异步的结果(就加了个async,实际应该什么没用的,文档中明确说了,只有函数内部使用了异步函数且需要同步返回时,也就是需要在内部用await时,才需要定义async)。
结果如下:
flask
Transactions: 4579 hits Availability: 100.00 % Elapsed time: 9.15 secs Data transferred: 0.11 MB Response time: 0.03 secs Transaction rate: 500.66 trans/sec Throughput: 0.01 MB/sec Concurrency: 14.93 Successful transactions: 4579 Failed transactions: 0 Longest transaction: 0.10 Shortest transaction: 0.02
flask + waitress
Transactions: 12598 hits Availability: 100.00 % Elapsed time: 10.02 secs Data transferred: 0.31 MB Response time: 0.01 secs Transaction rate: 1257.03 trans/sec Throughput: 0.03 MB/sec Concurrency: 14.89 Successful transactions: 12598 Failed transactions: 0 Longest transaction: 0.03 Shortest transaction: 0.00
fastapi + uvicorn
Transactions: 5278 hits Availability: 100.00 % \Elapsed time: 9.05 secs Data transferred: 0.09 MB Response time: 0.03 secs Transaction rate: 583.20 trans/sec Throughput: 0.01 MB/sec Concurrency: 14.93 Successful transactions: 5278 Failed transactions: 0 Longest transaction: 0.11 Shortest transaction: 0.01
fastapi + uvicorn + async
Transactions: 5876 hits Availability: 100.00 % \Elapsed time: 9.31 secs Data transferred: 0.10 MB Response time: 0.02 secs Transaction rate: 631.22 trans/sec Throughput: 0.01 MB/sec Concurrency: 14.84 Successful transactions: 5876 Failed transactions: 0 Longest transaction: 0.12 Shortest transaction: 0.00
从Transaction rate也就是请求处理速率可以看到:
- Flask直接启动结果比FastAPI启动结果略差一些(500:583/631)
- FastAPI用不用异步async差别不大(583:631)
- Flask用waitress WSGI服务器启动结果比不用快了2.5倍(1257:500),同样也比FastAPI快2倍左右
这个结果和其他人测试的完全不同,与我预估的也有很大差距,感觉是哪里出错了?
Flask直接启动比FastAPI慢是在意料之中的,但是使用waitress WSGI服务器启动后快这么多肯定也是不正常的。
于是我去查看了二者启动的源码,发现waitress默认4线程,uvicorn默认1线程。。。
只好把Flask修改为1线程重新测试
serve(app, host='0.0.0.0', port=5000, threads=1)
结果如下:
Transactions: 7492 hits
Availability: 100.00 %
Elapsed time: 9.07 secs
Data transferred: 0.19 MB
Response time: 0.02 secs
Transaction rate: 825.84 trans/sec
Throughput: 0.02 MB/sec
Concurrency: 14.89
Successful transactions: 7492
Failed transactions: 0
Longest transaction: 0.07
Shortest transaction: 0.01
把uvicorn修改为4线程重新测试
uvicorn.run("test-fastapi:app", host="0.0.0.0", port=5000, workers=4)
# 需要同目录下新建`pyproject.toml`文件,内容为:
[tool.poetry.scripts]
start = "test-fastapi:start"
结果如下:
Transactions: 7782 hits
Availability: 100.00 %
Elapsed time: 9.24 secs
Data transferred: 0.13 MB
Response time: 0.02 secs
Transaction rate: 842.39 trans/sec
Throughput: 0.01 MB/sec
Concurrency: 14.92
Successful transactions: 7782
Failed transactions: 0
Longest transaction: 0.15
Shortest transaction: 0.00
可以看出:
- Flask用waitress WSGI服务器单线程启动结果比不用快了65%(825:500),同样也比FastAPI快很多(825:583/631)
- uvicorn用4线程启动提升很小(842:583/631),还没有waitress单线程快
这个结果很出乎意料,我现在有点不自信了,是我测试过程哪里不对吗?
理论上说不通啊,uvicorn开4线程后结果只有1倍多,waitress开4线程快了2倍多,代表着4线程都没完全利用到,而且uvicorn单线程足处理能力更强吧,不知道为什么结果差这么多。
可能是测试工具的原因吧,毕竟别人都用的AB,还都指定并发数,我用的siege,没限制并发。
而且uvicorn文档还提到可以使用Gunicorn管理进程,可能性能还会提升,碍于设备原因我就不测试了。
换工具重新测试
实在信不过这个结果,第二天又换了hey压测工具测试一次,20并发10秒
.\hey_windows_amd64.exe -c 20 -z 10s http://127.0.0.1:5000/
结果如下:
flask
Summary: Total: 10.0802 secs Slowest: 0.1169 secs Fastest: 0.0056 secs Average: 0.0389 secs Requests/sec: 512.5894
flask + waitress 1线程
Summary: Total: 10.0128 secs Slowest: 0.0721 secs Fastest: 0.0013 secs Average: 0.0199 secs Requests/sec: 1006.4087
fastapi + uvicorn 1线程
Summary: Total: 10.0161 secs Slowest: 0.0888 secs Fastest: 0.0031 secs Average: 0.0225 secs Requests/sec: 886.4733
fastapi + uvicorn + async 1线程
Summary: Total: 10.0218 secs Slowest: 0.0808 secs Fastest: 0.0057 secs Average: 0.0210 secs Requests/sec: 951.3288
flask + waitress 4线程
Summary: Total: 10.0051 secs Slowest: 0.0912 secs Fastest: 0.0016 secs Average: 0.0134 secs Requests/sec: 1486.4384
fastapi + uvicorn 4线程
Summary: Total: 10.0386 secs Slowest: 0.0925 secs Fastest: 0.0018 secs Average: 0.0154 secs Requests/sec: 1292.3074
fastapi + uvicorn + async 4线程
Summary: Total: 10.0108 secs Slowest: 0.0853 secs Fastest: 0.0016 secs Average: 0.0134 secs Requests/sec: 1489.9933
从结果QPS和平均响应时间可以看出:
- Flask直接启动结果比FastAPI启动结果差了接近一半
- FastAPI用异步async还是有一些提升的
- Flask用waitress WSGI服务器启动与FastAPI用uvicorn+异步结果基本相当
- Flask用waitress WSGI服务器单线程启动结果比之前快了近1倍,也比FastAPI快一点
写在最后
做这个测试的本意是反驳前文提到的原因,只是想说,对比测试时应该使用第三方WSGI服务器启动Flask。
最终测试结果我也有点迷糊了,只能保证测试数据和代码绝对真实,看到本文的朋友最好自己测试一遍。
另外,性能测试肯定要加上基本功能,起码要有数据接收、处理、返回整个流程吧,只测试HelloWorld没什么代表性。
还是多说一句,网上太多无脑吹FastAPI的人了,不否认它的优点,比如支持异步、ws、自动生成文档、强调声明变量类型等,但也没必要死踩Flask性能上位。
连写文带测试花了几个小时,闲的。