簡體   English   中英

FastAPI UploadFile 比 Flask 慢

[英]FastAPI UploadFile is slow compared to Flask

我已經創建了一個端點,如下所示:

@app.post("/report/upload")
def create_upload_files(files: UploadFile = File(...)):
        try:
            with open(files.filename,'wb+') as wf:
                wf.write(file.file.read())
                wf.close()
        except Exception as e:
            return {"error": e.__str__()}

它與 uvicorn 一起啟動:

../venv/bin/uvicorn test_upload:app --host=0.0.0.0 --port=5000 --reload

我正在執行一些使用 Python 請求上傳大約100 MB文件的測試,大約需要 128 秒:

f = open(sys.argv[1],"rb").read()
hex_convert = binascii.hexlify(f)
items = {"files": hex_convert.decode()}
start = time.time()
r = requests.post("http://192.168.0.90:5000/report/upload",files=items)
end = time.time() - start
print(end)

我使用 Flask 使用 API 端點測試了相同的上傳腳本,大約需要 0.5 秒:

from flask import Flask, render_template, request
app = Flask(__name__)


@app.route('/uploader', methods = ['GET', 'POST'])
def upload_file():
   if request.method == 'POST':
      f = request.files['file']
      f.save(f.filename)
      return 'file uploaded successfully'

if __name__ == '__main__':
    app.run(host="192.168.0.90",port=9000)

有什么我做錯了嗎?

您可以使用async定義端點,並且由於所有UploadFile方法都是async方法,因此您需要await它們。 您可以使用同步寫入來寫入文件,如this answer所示,或者(更好)使用aiofiles異步寫入,如下所示:

上傳單個文件

應用程序.py

from fastapi import File, UploadFile
import aiofiles

@app.post("/upload")
async def upload(file: UploadFile = File(...)):
    try:
        contents = await file.read()
        async with aiofiles.open(file.filename, 'wb') as f:
            await f.write(contents)
    except Exception:
        return {"message": "There was an error uploading the file"}
    finally:
        await file.close()

    return {"message": f"Successfuly uploaded {file.filename}"}

以分塊方式異步,以避免將整個文件加載到 memory中。 不過,這需要更長的時間才能完成(取決於您選擇的塊大小)。

from fastapi import File, UploadFile
import aiofiles

@app.post("/upload")
async def upload(file: UploadFile = File(...)):
    try:
        async with aiofiles.open(file.filename, 'wb') as f:
            while contents := await file.read(1024): # async read chunk
                await f.write(contents)
    except Exception:
        return {"message": "There was an error uploading the file"}
    finally:
        await file.close()

    return {"message": f"Successfuly uploaded {file.filename}"}

測試.py

import requests

url = 'http://127.0.0.1:8000/upload'
file = {'file': open('images/1.png', 'rb')}
resp = requests.post(url=url, files=file) 
print(resp.json())

上傳多個文件

應用程序.py

from fastapi import File, UploadFile
import aiofiles

@app.post("/upload")
async def upload(files: List[UploadFile] = File(...)):
    for file in files:
        try:
            contents = await file.read()
            async with aiofiles.open(file.filename, 'wb') as f:
                await f.write(contents)
        except Exception:
            return {"message": "There was an error uploading the file(s)"}
        finally:
            await file.close()

    return {"message": f"Successfuly uploaded {[file.filename for file in files]}"}  

測試.py

import requests

url = 'http://127.0.0.1:8000/upload'
files = [('files', open('images/1.png', 'rb')), ('files', open('images/2.png', 'rb'))]
resp = requests.post(url=url, files=files) 
print(resp.json())

更新

深入研究源代碼,似乎最新版本的Starlette (FastAPI 在下面使用)使用了一個SpooledTemporaryFile (用於UploadFile數據結構),其max_size屬性設置為1 MB (1024 * 1024 字節) - 見這里- 與舊版本相比max_size設置為默認值的版本,即 0 字節,例如此處的那個。

上面的意思是,過去,無論文件大小如何,數據都被完全加載到 memory 中(當文件無法放入 RAM 時可能會導致問題),而在最新版本中,數據被假脫機memory 直到file大小超過max_size (即1 MB),此時將內容寫入磁盤; 更具體地說,到操作系統的臨時目錄(注意:這也意味着您可以上傳的文件的最大大小受系統臨時目錄可用的存儲空間的限制。如果您的系統上有足夠的存儲空間(滿足您的需要),沒有什么可擔心的;否則,請查看有關如何更改默認臨時目錄的答案)。 因此,多次寫入文件的過程 - 即首先將數據加載到 RAM 中,然后,如果數據大小超過 1 MB,則將文件寫入臨時目錄,然后從臨時目錄中讀取文件(使用file.read() )最后,將文件寫入永久目錄 - 與使用 Flask 框架相比,上傳文件速度較慢,正如 OP 在他們的問題中指出的那樣(盡管時間差異不是那么大,但只有幾秒鍾,取決於文件的大小)。

解決方案

解決方案(如果需要上傳大於 1 MB 的文件並且上傳時間對他們很重要)將作為 stream 訪問request正文。 根據Starlette 文檔,如果您訪問.stream() ,則提供字節塊而不將整個主體存儲到 memory (如果主體包含超過 1 MB 的文件數據,則稍后存儲到臨時目錄)。 下面給出了示例,其中上傳時間記錄在客戶端,最終與使用 Flask 框架以及 OP 問題中給出的示例時相同。

應用程序.py

from fastapi import Request
import aiofiles

@app.post('/upload')
async def upload(request: Request):
    try:
        filename = request.headers['filename']
        async with aiofiles.open(filename, 'wb') as f:
            async for chunk in request.stream():
                await f.write(chunk)
    except Exception:
        return {"message": "There was an error uploading the file"}
     
    return {"message": f"Successfuly uploaded {filename}"}

如果您的應用程序不需要將文件保存到磁盤,而您只需要將文件直接加載到 memory 中,則可以使用以下內容(確保您的 RAM 有足夠的空間來容納累積的數據):

from fastapi import Request

@app.post('/upload')
async def upload(request: Request):
    body = b''
    try:
        filename = request.headers['filename']
        async for chunk in request.stream():
            body += chunk
    except Exception:
        return {"message": "There was an error uploading the file"}
    
    #print(body.decode())
    return {"message": f"Successfuly uploaded {filename}"}

測試.py

import requests
import time

with open("images/1.png", "rb") as f:
    data = f.read()
   
url = 'http://127.0.0.1:8000/upload'
headers = {'filename': '1.png'}

start = time.time()
resp = requests.post(url=url, data=data, headers=headers)
end = time.time() - start

print(f'Elapsed time is {end} seconds.', '\n')
print(resp.json())

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM