Mar*_*aci 11 python wsgi flask gunicorn waitress
我正在尝试创建一个 Flask 应用程序,它应该:
我做了一个快速测试并使用 Flask 开发服务器运行它,它按预期工作。由于被红色文字吓到,WARNING: This is a development server. Do not use it in a production deployment.我尝试将其放在 WSGI 服务器后面,但 Waitress 和 GUnicorn 的结果都慢得多。测试(针对具有人工输入、微小输出和完全可复制代码的玩具问题)如下。
我把这三个文件放在一个文件夹中:
basic_flask_app.py(这里应该对它获取的数据做很少的事情;我拥有的真正代码是一个在 GPU 上运行得相当快的深度学习模型,但创建这个示例是为了使问题更加极端)
import numpy as np
from flask import Flask, request
from do_request import IS_SMALL_DATA, WIDTH, HEIGHT
app = Flask(__name__)
@app.route('/predict', methods=['POST'])
def predict():
numpy_bytes = np.frombuffer(request.data, np.float32)
if IS_SMALL_DATA:
numpy_image = np.zeros((HEIGHT, WIDTH)) + numpy_bytes
else:
numpy_image = numpy_bytes.reshape(HEIGHT, WIDTH)
result = numpy_image.mean(axis=1).std(axis=0)
return result.tobytes()
if __name__ == '__main__':
app.run(host='localhost', port=80, threaded=False, processes=1)
Run Code Online (Sandbox Code Playgroud)
[编辑:这个问题的原始版本缺少threaded=False, processes=1上面调用中的参数app.run,因此行为与下面的 GUnicorn 和 Waitress 不同,它们被迫单线程/进程;我现在添加了它,并重新测试,结果没有改变,Flask 服务器在此更改后仍然很快 - 如果有的话,更快]
do_request.py
import requests
import numpy as np
from tqdm import trange
WIDTH = 2500
HEIGHT = 3000
IS_SMALL_DATA = False
def main(url='http://127.0.0.1:80/predict'):
n = WIDTH * HEIGHT
if IS_SMALL_DATA:
np_image = np.zeros(1, dtype=np.float32)
else:
np_image = np.arange(n).astype(np.float32) / np.float32(n)
results = []
for _ in trange(50):
results.append(requests.post(url, data=np_image.tobytes()))
if __name__ == '__main__':
main()
Run Code Online (Sandbox Code Playgroud)
女服务员服务器.py
from waitress import serve
import basic_flask_app
serve(basic_flask_app.app, host='127.0.0.1', port=80, threads=1)
Run Code Online (Sandbox Code Playgroud)
python do_requests.py我使用以下三个命令之一启动模型后运行了测试:
python basic_flask_app.py
python waitress_server.py
gunicorn -w 1 basic_flask_app:app -b 127.0.0.1:80
Run Code Online (Sandbox Code Playgroud)
通过这三个选项,并切换IS_SMALL_DATA标志(如果为 True,则仅传输 4 个字节的数据;如果为 False,则传输 30MB),我得到以下计时:
50 requests Flask Waitress GUnicorn
30MB input, 4B output: 00:01 (28.6 it/s) 00:11 (4.42 it/s) 00:11 (4.26 it/s)
4B input, 4B output: 00:01 (25.2 it/s) 00:02 (23.6 it/s) 00:01 (26.4 it/s)
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,Flask 开发服务器速度非常快,与传输的数据量无关(“小”数据甚至有点慢,可能是因为它浪费了在 50 次迭代中每次分配内存的时间),而 Waitress 和随着传输数据的增加,GUnicorn 的速度受到显着影响。
此时,我有几个问题:
这很有趣。也许这可以解释这个问题。
通过使用 time.time() 我发现request.data网络应用程序花费了不同的时间。当使用 Gunicorn 时,这会花费超过 95% 的时间,即 0.35 秒。当使用 Flask Web 应用程序时,这花费了大约 0.001 秒。
我走进它的包裹。我发现大部分时间werkzeug/wrappers/base_request.py 456 line花在
rv = self.stream.read()
使用 Flask 开发服务器时。这self.stream是werkzeug.wsgi.LimitedStream。这条线花费了大约0.001s。
使用枪角兽时。这self.stream是gunicorn.http.body.Body。这将花费超过0.3秒。
我步入gunicorn/http/body.py。第 214-218 行
while size > self.buf.tell():
data = self.reader.read(1024)
if not data:
break
self.buf.write(data)
Run Code Online (Sandbox Code Playgroud)
这花费了超过0.3s。
我尝试将上面的代码更改为self.buf.write(self.reader.read(size)). 这使得它花费了 0.07 秒。
我将上面的代码分成
now = time.time()
buffer = self.reader.read(size)
print(time.time() - now)
now = time.time()
Run Code Online (Sandbox Code Playgroud)
我发现第一行成本为 0.053。第二行成本 0.017。
我想我已经找到原因了。
首先,gunicorn 使用 io.BytesIO 将原始字节包装到他的特殊对象中。
其次,gunicorn 使用 while 循环读取字节,这会花费更多时间。
我猜这些代码的目的是支持高并发。
对于你的情况,我认为你可以直接使用 gevent 。
from gevent.pywsgi import WSGIServer
from basic_flask_app import app
http_server = WSGIServer(('', 80), app)
http_server.serve_forever()
Run Code Online (Sandbox Code Playgroud)
这要快得多。