• Speed Up Your Python Code With 100% Thread Utilization Using This Library
  • By Haseeb Kamal
  • The Nuggets translation Project
  • Permanent link to this article: github.com/xitu/gold-m…
  • Translator: Z Zhaojin
  • Proofreader: Samyu2000, jjCatherine

Use FastAPI to improve the performance of Python code

There are many Web frameworks for Python, the most popular being Django and Flask. Flask is the one I’m most familiar with, and I use it a lot to do side projects and improve my coding skills.

However, as technology evolves, new frameworks are emerging. As a programmer, it is necessary to know the most advanced technology and keep pace with the development of The Times.

FastAPI is a Python Web framework similar in many ways to simple Flask. FastAPI is different in that it runs on ASGI web servers (such as Uvicorn), whereas Flask only runs on WSGI web servers. This difference can result in vastly different performance between the two.

The use of ASGI is a trend, and it’s important that you understand it. Using FastAPI and ASGI, it is very easy to develop high-performance Python applications. This article will teach you how to develop a simple API based on the FastAPI framework, and then compare it to Flask to see how fast FastAPI really is.

Let’s get started!

First, a brief introduction to WSGI and ASGI.

WSGI with ASGI

WSGI stands for Web Server Gateway Interface. Simply put, WSGI sits between a Web server (such as Nginx) and Python’s Web framework (such as Flask). It specifies how the Web server should forward requests to the Web framework. WSGI was first released in 2003, so you can imagine how old it is. WSGI is synchronous in nature, which can lead to slow execution.

ASGI stands for Asynchronous Server Gateway Interface. The trend is that ASGI will replace WSGI. The key difference between the two is that ASGI supports Web frameworks with asynchronous code. That is, it is itself asynchronous. If your code is executed asynchronously (e.g. with async await), execution will also be faster.

Confused? Don’t worry. Let me illustrate the difference between synchronous and asynchronous execution, as shown below:

Comparison of synchronous and asynchronous operation modes. Note that asynchronous execution can save a lot of time.

As you can see, in synchronous Execution mode (WSGI), a thread can only handle one request at a time. Therefore, if there is a block during the execution of the current request and some execution result is to be waited for, a lot of time can be wasted (as in the waiting block above). The thread can only start task B when the entire task A is complete.

On the other hand, in asynchronous execution mode (ASGI), a single thread can process multiple requests. When task A is executed, the thread can jump to task B, complete task B as long as task A waits, and then jump back to task A and complete it. From the figure above, we can see that a lot of time can be saved by executing this way asynchronously. More importantly, by executing asynchronously, we make full use of threads, which means less waiting time and more tasks can be performed. This greatly improves the performance of the application.

In short: with asynchronous code, threads can do more work in the same amount of time. The performance gains come from applications that do more work per unit of time.

Comparison and analysis

It works in theory, but it would be more interesting to have a real-world example that illustrates the difference in performance.

Install the FastAPI library first:

pip3 install fastapi[all]
Copy the code

Based on FastAPI, write the following code:

import os
from fastapi import FastAPI
import time
import pandas as pd
import pickle
import os
import asyncio
from concurrent.futures import ThreadPoolExecutor
import asyncio
import threading

app = FastAPI()

# @app.on_event("startup")
# async def startup_event():
# loop = asyncio.get_running_loop()
# loop.set_default_executor(ThreadPoolExecutor(max_workers=1))

@app.get("/dummy")
async def dummy() :
    print(threading.active_count())
    return {"message": "async power"}

@app.get("/")
async def func() :
    await helper()
    return {"message": "done"}

async def helper() :
    await asyncio.sleep(20)
    # time.sleep(20)
    return heavy_func()

def heavy_func() :
    # do stuff
    return "ok"
Copy the code

We have two endpoints: root endpoint/and an alternate endpoint dummy. The root endpoint does some of the heavy lifting, which we simulate by setting a 20-second timer.

We define the endpoint itself as asynchronous, and use the async keyword to indicate that the thread can do other work when the call completes and returns. We also use asyncio’s sleep function because it supports async and await. The dummy endpoint simply returns a message.

As you can see, defining an API in FastAPI is very simple. We just need to initialize a FastAPI application and define the endpoint with the @app function modifier.

Here is the code for Flask:

import os
from fastapi import FastAPI
import time
import pandas as pd
import pickle
import os
import asyncio
from concurrent.futures import ThreadPoolExecutor
import asyncio
import threading

app = FastAPI()

# @app.on_event("startup")
# async def startup_event():
# loop = asyncio.get_running_loop()
# loop.set_default_executor(ThreadPoolExecutor(max_workers=1))

@app.get("/dummy")
async def dummy() :
    print(threading.active_count())
    return {"message": "async power"}

@app.get("/")
async def func() :
    await helper()
    return {"message": "done"}

async def helper() :
    await asyncio.sleep(20)
    # time.sleep(20)
    return heavy_func()

def heavy_func() :
    # do stuff
    return "ok"
Copy the code

This code is the same as the code for FastAPI.

The idea is to call the root endpoint, and while it waits 20 seconds, we call the dummy endpoint.

The key point is this: if the interface is asynchronous (ASGI), a call to dummy should immediately return the corresponding result.

Note: We set the threaded flag in Flask to false because we only want to test the performance of a single thread to learn. For multithreaded applications, the key points should be the same.

Start the FastAPI service with:

uvicorn tfastapi:app --reload
Copy the code

You can check in the console which port it is running on. The default is port 8000.

Next, open two terminal Windows. (I use PowerShell on Windows, but you can also use Git Bash, Linux, or macOS terminals)

Run the following curl command on the first terminal:

curl http://localhost:8000/
Copy the code

On the second terminal, run the following curl command to generate the dummy endpoint:

curl http://localhost:8000/dummy
Copy the code

Press Enter in the first window, and then Enter in the second window. You should notice that a request to dummy returns results almost immediately.

Request an asynchronous endpoint in FastAPI and get an immediate response.

If you wait 20 seconds, the call to root should return.

What’s going on here? The thread handles the call to root first. When the sleep function is executed, it is in a wait state with a request to the dummy endpoint. The thread then executes the dummy function instead. Once it is processed, the thread returns to handle the request to root.

Now let’s see what happens to Flask.

You can run the Flask server as follows:

py tflask.py
Copy the code

It should run on port 5000.

Next, copy and paste the curl request to root, and then copy the curl request to dummy as before. Run the root request first, then the other one.

You’ll notice that the call to dummy does not return immediately! Both requests take 20 seconds to return. What’s going on here?

Flask uses WSGI implementations of server-Framework interfaces, even though the code is asynchronous. This means that the endpoints in Flask are not truly asynchronous. We make a request to root, and it waits. When we make a second request to the thread, the thread does not jump to process the request. The program pauses for 20 seconds and then continues with the rest of the code in the root request. In other words, the program pauses for 20 seconds before executing another request. During this time, the thread does nothing but wait! It’s inefficient. It’s too inefficient!

This paper summarizes

In this article, we learned about synchronous versus asynchronous code and the implementation of WSGI and ASGI interfaces.

We saw how FastAPI can help achieve full thread utilization and greatly speed up code execution.

All you need to do with FastAPI is use async in front of each endpoint and make sure your code is asynchronous. Actually, I’d like to expand on that last point and conclude with a few things.

  • ASGI is a new technology, so it may be difficult to find relevant information. It also has fewer reviews than WSGI.
  • If you want to get a performance boost, you need to use asynchronous code, which not all libraries currently support. For example, some Python database dependencies are only implemented synchronously. In this case, you probably won’t get much of a performance boost.
  • Finally, FastAPI supports type hints and is fairly well integrated with Pydantic. You can check out my post here to learn more about this.

Thank you for reading!

If you find any mistakes in your translation or other areas that need to be improved, you are welcome to the Nuggets Translation Program to revise and PR your translation, and you can also get the corresponding reward points. The permanent link to this article at the beginning of this article is the MarkDown link to this article on GitHub.


The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.