Skip to content

Commit 8b352f2

Browse files
committed
Add in FastAPI as a benchmark
* Introduce FastAPI as a new benchmark * Add a new gunicorn benchmark for ninja * Remove graph as it becomes stale too easily * Sort imports * Remove unused code from run_test.py * Intentionally unfreeze the requirements.txt - this is to allow for the benchmark to run with the latest versions.
1 parent 6ab2bca commit 8b352f2

File tree

7 files changed

+103
-37
lines changed

7 files changed

+103
-37
lines changed

Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM python:3.8.2
1+
FROM python:3
22

33
ENV PYTHONUNBUFFERED=1 PIP_DISABLE_PIP_VERSION_CHECK=on
44

@@ -9,4 +9,5 @@ COPY common_django_settings.py /common_django_settings.py
99
COPY app_drf /app_drf
1010
COPY app_flask_marshmallow /app_flask_marshmallow
1111
COPY app_ninja /app_ninja
12+
COPY app_fastapi /app_fastapi
1213
COPY network_service.py /network_service.py

README.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,15 @@ python run_test.py
1616

1717
### Results
1818

19-
![Django Ninja REST Framework](img/results.png)
19+
```text
20+
Framework : 1 2 4 8 16 32 64
21+
drf_uwsgi : 9.37 18.95 37.15 70.35 123.77 209.73 234.63
22+
fastapi_gunicorn : 9.57 17.52 36.13 52.74 113.62 208.13 262.85
23+
fastapi_uvicorn : 9.50 17.94 31.52 55.63 118.94 167.54 194.12
24+
flask_marshmallow_uwsgi : 9.53 18.75 37.04 72.74 131.16 229.92 326.55
25+
ninja_gunicorn : 240.49 278.57 317.09 309.32 285.71 246.44 256.98
26+
ninja_uvicorn : 265.31 289.09 280.00 305.05 247.62 237.71 212.58
27+
ninja_uwsgi : 9.29 18.56 36.04 68.98 128.32 180.89 182.31
28+
Columns indicate the number of workers in each run
29+
Values are in requests per second - higher is better.
30+
```

app_fastapi/main.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import datetime
2+
from typing import List
3+
4+
import requests
5+
from fastapi import FastAPI
6+
from pydantic import BaseModel, Field
7+
8+
9+
class Location(BaseModel):
10+
latitude: float | None = None
11+
longitude: float | None = None
12+
13+
14+
class Skill(BaseModel):
15+
subject: str
16+
subject_id: int
17+
category: str
18+
qual_level: str
19+
qual_level_id: int
20+
qual_level_ranking: float = 0
21+
22+
23+
class Model(BaseModel):
24+
id: int
25+
client_name: str = Field(max_length=255)
26+
sort_index: float
27+
client_phone: str | None = Field(None, max_length=255)
28+
location: Location
29+
contractor: int | None = Field(None, gt=0)
30+
upstream_http_referrer: str | None = Field(None, max_length=1023)
31+
grecaptcha_response: str = Field(min_length=20, max_length=1000)
32+
last_updated: datetime.datetime | None
33+
skills: List[Skill]
34+
35+
36+
app = FastAPI()
37+
38+
39+
@app.post("/api/create")
40+
async def create(data: Model):
41+
return {"success": True}, 201
42+
43+
44+
@app.get("/api/iojob")
45+
async def iojob():
46+
response = requests.get('http://network_service:8000/job')
47+
assert response.status_code == 200
48+
return {"success": True}, 200

docker-compose.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,21 @@ services:
3030
working_dir: /app_ninja
3131
command: uvicorn djninja.asgi:application --host 0.0.0.0 --workers ${WORKERS}
3232

33+
ninja_gunicorn:
34+
<<: *common
35+
working_dir: /app_ninja
36+
command: gunicorn djninja.asgi:application --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000 --workers ${WORKERS}
37+
38+
fastapi_gunicorn:
39+
<<: *common
40+
working_dir: /app_fastapi
41+
command: gunicorn main:app --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000 --workers ${WORKERS}
42+
43+
fastapi_uvicorn:
44+
<<: *common
45+
working_dir: /app_fastapi
46+
command: uvicorn main:app --host 0.0.0.0 --workers ${WORKERS}
47+
3348
network_service:
3449
build: .
3550
command: python network_service.py > /dev/null

img/results.png

-50.7 KB
Binary file not shown.

requirements.txt

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
1-
Django==3.1b1
1+
Django
22

3-
django-ninja==0.2.0
4-
pydantic==1.5.1
3+
django-ninja
4+
fastapi[standard]
5+
djangorestframework
6+
Flask
57

6-
djangorestframework==3.11.0
8+
pydantic
9+
marshmallow
10+
sanic
711

8-
Flask==1.1.2
9-
marshmallow==3.6.1
10-
11-
sanic==20.6.3
12-
13-
uvicorn==0.11.5
14-
uWSGI==2.0.19.1
15-
16-
aiohttp==3.6.2
12+
uvicorn[standard]
13+
gunicorn
14+
uWSGI
1715

16+
aiohttp
1817
requests

run_test.py

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,16 @@
1-
import time
2-
import re
31
import os
4-
import sys
2+
import re
53
import subprocess
6-
7-
8-
C1_FRAMEWORKS = [
9-
'flask_marshmallow_uwsgi',
10-
'drf_uwsgi',
11-
'ninja_uwsgi',
12-
]
4+
import time
135

146
CONCURRENT_FRAMEWORKS = [
157
'flask_marshmallow_uwsgi',
168
'drf_uwsgi',
9+
'ninja_uwsgi',
10+
'ninja_gunicorn',
1711
'ninja_uvicorn',
12+
'fastapi_gunicorn',
13+
'fastapi_uvicorn'
1814
]
1915

2016

@@ -45,29 +41,21 @@ def benchmark(url, concurency, count, payload=None):
4541

4642

4743
def parse_benchmark(output: str):
48-
# print(output)
4944
rps = re.findall(r'Requests per second: \s+(.*?)\s', output)[0]
5045
p50 = re.findall(r'\s+50%\s+(\d+)', output)[0]
5146
p99 = re.findall(r'\s+99%\s+(\d+)', output)[0]
52-
return (rps, p50, p99)
53-
# print()
54-
# re.findall(r'')
47+
return rps, p50, p99
5548

5649

5750
def preheat():
5851
benchmark('http://127.0.0.1:8000/api/create', 1, 5, 'payload.json')
5952
benchmark('http://127.0.0.1:8000/api/iojob', 1, 5)
6053

6154

62-
def run_c1_test():
63-
return benchmark('http://127.0.0.1:8000/api/create', 1, 1000, 'payload.json')
64-
65-
66-
WORKERS_CASES = list(range(1, 25)) # [14, 15, 16, 17, 18, 19, 20]
55+
WORKERS_CASES = [1, 2, 4, 8, 16, 32, 64]
6756

6857

6958
def test_concurrent(name):
70-
7159
results = {}
7260
for workers in WORKERS_CASES:
7361
with FrameworkService(name, workers):
@@ -88,13 +76,17 @@ def main():
8876
print('Framework :', end='')
8977
for w in WORKERS_CASES:
9078
print(f'{w:>9}', end='')
91-
print('')
79+
80+
print()
81+
9282
for framework, results in sorted(results.items()):
9383
print(f'{framework:<23} :', end='')
9484
for w in WORKERS_CASES:
9585
print(f'{results[w][0]:>9}', end='')
96-
print('')
86+
print()
9787

88+
print('Columns indicate the number of workers in each run')
89+
print('Values are in requests per second - higher is better.')
9890

9991
if __name__ == '__main__':
10092
main()

0 commit comments

Comments
 (0)