fastapi: FastAPI+Uvicorn is running slow than Flask+uWSGI

I’m new to fastapi and I’m trying to test speed between fastapi and flask, but I didn’t get a better result by fastapi. pls tell me if I’m making anything wrong?

Example

  1. fastapi
from fastapi import FastAPI

app = FastAPI(debug=False)

@app.get("/")
async def run():
    return {"message": "hello"}
  • run command: uvicorn --log-level error --workers 4 fastapi_test:app > /dev/null 2>&1
  1. flask
import flask

app = flask.Flask(__name__)

@app.route("/")
def run():
    return {"message": "hello"}
  • run command: uwsgi --wsgi-file flask_test.py --process 4 --callable app --http :8000 > /dev/null 2>&1

Result

  • use ab -n 10000 -c 500 http://127.0.0.1:8000/ to test speed
  1. FastApi
Requests per second:    1533.91 [#/sec] (mean)
Time per request:       325.965 [ms] (mean)
Time per request:       0.652 [ms] (mean, across all concurrent requests)
Transfer rate:          244.17 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0   46 208.1      0    1000
Processing:     1  268 171.1    245     950
Waiting:        0  201 146.1    174     909
Total:          1  314 296.7    246    1918
  1. Flask
Requests per second:    1829.40 [#/sec] (mean)
Time per request:       273.313 [ms] (mean)
Time per request:       0.547 [ms] (mean, across all concurrent requests)
Transfer rate:          162.57 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0   18 131.3      0    1000
Processing:    12  192 556.3     36    4302
Waiting:        0  191 556.3     35    4301
Total:         17  210 612.7     36    5300

Environment

  • OS: CentOS 7
  • Python Version: 3.9.1
  • FastAPI Version: 0.63.0

Additional context

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 1
  • Comments: 20 (8 by maintainers)

Most upvoted comments

I’m very interesting about this question. Considering the comments show different opinions, I decide to test by myself, there are the testing results:

  1. FastAPI + async def + uvicorn
from fastapi import FastAPI

app = FastAPI(debug=False)

@app.get("/")
async def run():
    return {"message": "hello"}
  • run command: uvicorn --log-level error --workers 4 fastapi_test:app > /dev/null 2>&1

Requests per second: 12160.04 [#/sec] (mean) Time per request: 41.118 [ms] (mean) Time per request: 0.082 [ms] (mean, across all concurrent requests) Transfer rate: 1935.63 [Kbytes/sec] received

  1. Flask + gunicorn
import flask

app = flask.Flask(__name__)

@app.route("/")
def run():
    return {"message": "hello"}
  • run command: gunicorn --log-level error -w 4 flask_test:app > /dev/null 2>&1

Requests per second: 15726.21 [#/sec] (mean) Time per request: 31.794 [ms] (mean) Time per request: 0.064 [ms] (mean, across all concurrent requests) Transfer rate: 2641.51 [Kbytes/sec] received

These first two testings show the same result as @Arrow-Li wrote at the begining.

  1. FastAPI + async def + gunicorn with uvicorn workers
from fastapi import FastAPI

app = FastAPI(debug=False)

@app.get("/")
async def run():
    return {"message": "hello"}
  • run command: gunicorn --log-level error -w 4 -k uvicorn.workers.UvicornWorker fastapi_test:app > /dev/null 2>&1

Requests per second: 34781.40 [#/sec] (mean) Time per request: 14.376 [ms] (mean) Time per request: 0.029 [ms] (mean, across all concurrent requests) Transfer rate: 4891.13 [Kbytes/sec] received

This is nearly 3x performance than test 1.

  1. FastAPI + def + uvicorn
from fastapi import FastAPI

app = FastAPI(debug=False)

@app.get("/")
def run():
    return {"message": "hello"}
  • run command: uvicorn --log-level error --workers 4 fastapi_test:app > /dev/null 2>&1

Requests per second: 19752.03 [#/sec] (mean) Time per request: 25.314 [ms] (mean) Time per request: 0.051 [ms] (mean, across all concurrent requests) Transfer rate: 2777.63 [Kbytes/sec] received

Change asyc def to def makes FastAPI faster than Flask.

  1. FastAPI + def + gunicorn with uvicorn workers
from fastapi import FastAPI

app = FastAPI(debug=False)

@app.get("/")
def run():
    return {"message": "hello"}
  • run command: gunicorn --log-level error -w 4 -k uvicorn.workers.UvicornWorker fastapi_test:app > /dev/null 2>&1

Requests per second: 20315.62 [#/sec] (mean) Time per request: 24.612 [ms] (mean) Time per request: 0.049 [ms] (mean, across all concurrent requests) Transfer rate: 2856.88 [Kbytes/sec] received

So, in conclusion, for a function that can be defined as both async and sync, the performance rank is:

  1. FastAPI + async def + gunicorn with uvicorn workers
  2. FastAPI + def + gunicorn with uvicorn workers
  3. FastAPI + def + uvicorn
  4. Flask + gunicorn
  5. FastAPI + async def + uvicorn

Why not testing with Framework+Redis+Database? Such a simple case can not tell the true.

For a more detailed benchmark, check TechEmpowers

Framework JSON 1-query 20-query Fortunes Updates Plaintext
fastapi 171,055 66,185 13,022 52,080 5,926 159,445
flask 63,026 34,217 6,647 23,136 1,327 83,398

So to reach max performance should async + gunicorn

The answer is no. You should pick the one that fits your case. If you run your ML/DL model in a coroutine (async def endpoint), congrats, you will have a blocking endpoint and that endpoint will block your entire event loop.

async def endpoints does not mean it will be faster, that is not the point of `asynchronous I/O.

I think understanding asynchronous I/O a little bit deeper could help, so i’m copying this from one of my answer in Stackoverflow.


The question completely depends on what your function does and how it does.

Okay, but i need to understand asyncio better.

Then assume you have the following code

async def x():
    a = await service.start()
    return a
  1. This will allocate the stack space for the yielding variable of service().start()
  2. The event loop will execute this and jump to the next statement
    1. once start() get’s executed it will push the value of the calling stack
    2. This will store the stack and the instruction pointer.
    3. Then it will store the yielded variable from service().start() to a, then it will restore the stack and the instruction pointer.
  3. When it comes to return a this will push the value of a to calling stack.
  4. After all it will clear the stack and the instruction pointer.

Note that we were able to do all this because service().start() is a coroutine, it is yielding instead of returning.

This may not be clear to you at first glance but as I mentioned async and await are just fancy syntax for declaring and managing coroutines.

import asyncio

@asyncio.coroutine
def decorated(x):
    yield from x 

async def native(x):
    await x 

But these two function are identical does the exact same thing. You can think of yield from chains one and more functions together.

But to understand asynchronous I/O deeply we need to have an understanding of what it does and how it does underneath.

In most operating systems, a basic API is available with select() or poll() system calls.

These interfaces enable the user of the API to check whether there is any incoming I/O that should be attended to.

For example, your HTTP server wants to check whether any network packets have arrived in order to service them. With the help of this system calls you are able to check this.

When we check the manual page of select() we will see this description.

select() and pselect() allow a program to monitor multiple file de‐ scriptors, waiting until one or more of the file descriptors become “ready” for some class of I/O operation (e.g., input possible). A file descriptor is considered ready if it is possible to perform a corre‐ sponding I/O operation

This gives you a pretty basic idea, and this explains the nature of what asynchronous I/O does.

It lets you check whether descriptors can be read and can be written.

It makes your code more scalable, by not blocking other things. Your code becomes faster as a bonus, but it is not the actual purpose of asynchronous I/O.

So to tidy up.

The event loop just keeps yielding, while something is ready. By doing that it does not block.

You can’t realistically compare fastapi to flask anyway, as they are intended to do different things. Flask is designed for general websites with no real specialisation, whereas FastAPI has many built in features to specifically aid in the construction of rest-ish APIs.

Well… you’re using “async def” in the FastAPI example when you’re doing zeeo asynchronous operations in the endpoint. Try make it a normal function, then re-run the benchmarks.

@dstlny It will be even costly if you do that.

I believe using Uvicorn’s Gunicorn worker class along with gunicorn offers more performance than the uvicorn workers