Skip to content

Commit 3b8b471

Browse files
authored
Add benchmark history for better visibility (#65)
1 parent 49a8481 commit 3b8b471

23 files changed

Lines changed: 544 additions & 131 deletions

README.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -262,21 +262,21 @@ Compare the results! 👇
262262

263263
# Benchmarks 🏆
264264

265-
11th Gen Intel® Core™ i7-11800H @ 2.30GHz, 8 cores, 16 threads, 16GB RAM (Docker in Ubuntu 22.04.2 LTS)
265+
13th Gen Intel® Core™ i9-13900HK @ 5.40GHz, 14 cores, 20 threads, 32GB RAM (Docker in Ubuntu 22.04.2 LTS)
266266

267267
*(Sorted alphabetically)*
268268

269269
| Framework | "hello world" (req/s) | 99% latency (ms) | redis save (req/s) | 99% latency (ms) |
270270
| ----------- | --------------------- | ---------------- | ------------------ | ---------------- |
271-
| aiohttp | 14949.57 | 8.91 | 9753.87 | 13.75 |
272-
| aiozmq | 13844.67 | 9.55 | 5239.14 | 30.92 |
273-
| blacksheep | 32967.27 | 3.03 | 18010.67 | 6.79 |
274-
| fastApi | 13154.96 | 9.07 | 8369.87 | 15.91 |
275-
| sanic | 18793.08 | 5.88 | 12739.37 | 8.78 |
276-
| zero(sync) | 28471.47 | 4.12 | 18114.84 | 6.69 |
277-
| zero(async) | 29012.03 | 3.43 | 20956.48 | 5.80 |
278-
279-
Seems like blacksheep is faster on hello world, but in more complex operations like saving to redis, zero is the winner! 🏆
271+
| aiohttp | 30926.39 | 8.31 | 17092.79 | 10.86 |
272+
| aiozmq | 19076.34 | 8.65 | 7725.18 | 11.62 |
273+
| blacksheep | 22357.13 | 7.86 | 11253.14 | 18.26 |
274+
| fastApi | 20225.57 | 8.14 | 11362.2 | 17.78 |
275+
| sanic | 35917.24 | 5.88 | 22272.22 | 8.97 |
276+
| zero(sync) | 22248.28 | 9.44 | 11051.8 | 18.15 |
277+
| zero(async) | 27466.46 | 6.66 | 18625.51 | 12.04 |
278+
279+
Seems like sanic is the fastest in python 3.13
280280

281281
# Contribution
282282

benchmarks/benchmarks_md_table.py

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import os
2+
import re
3+
4+
available_benchmarks = [
5+
"benchmark-aiohttp",
6+
"benchmark-aiozmq",
7+
"benchmark-blacksheep",
8+
"benchmark-fastapi",
9+
"benchmark-sanic",
10+
"benchmark-zero",
11+
]
12+
13+
14+
def get_latest_history():
15+
# file format is `<available_benchmark>__<date>.log`
16+
history_dir = "dockerize/history"
17+
if not os.path.exists(history_dir):
18+
return []
19+
files = os.listdir(history_dir)
20+
files = [f for f in files if f.endswith(".log")]
21+
22+
latest_files = {}
23+
for f in files:
24+
benchmark_name = f.split("__")[0]
25+
if benchmark_name in available_benchmarks:
26+
date_str = f.split("__")[1].replace(".log", "")
27+
if (
28+
benchmark_name not in latest_files
29+
or date_str > latest_files[benchmark_name]
30+
):
31+
latest_files[benchmark_name] = date_str
32+
33+
return [f"{benchmark}__{date}.log" for benchmark, date in latest_files.items()]
34+
35+
36+
def parse_benchmark_log(filepath):
37+
results = []
38+
current_test = None
39+
latency_99 = None
40+
requests_sec = None
41+
42+
# Patterns to identify test type and metrics
43+
test_type_pattern = re.compile(r"Running 30s test @ http://gateway:8000/(\w+_?\w*)")
44+
latency_pattern = re.compile(r"99%\s+([\d.]+)ms")
45+
req_sec_pattern = re.compile(r"Requests/sec:\s+([\d.]+)")
46+
47+
with open(filepath, "r") as f:
48+
for line in f:
49+
# Identify the test type
50+
match_test = test_type_pattern.search(line)
51+
if match_test:
52+
current_test = match_test.group(1)
53+
continue
54+
55+
# Find 99% latency
56+
match_99 = latency_pattern.search(line)
57+
if match_99:
58+
latency_99 = float(match_99.group(1))
59+
continue
60+
61+
# Find Requests/sec
62+
match_req = req_sec_pattern.search(line)
63+
if match_req and current_test:
64+
requests_sec = float(match_req.group(1))
65+
results.append(
66+
{
67+
"test": current_test,
68+
"99%_latency_ms": latency_99,
69+
"requests_per_sec": requests_sec,
70+
}
71+
)
72+
# Reset for next run
73+
current_test = None
74+
latency_99 = None
75+
requests_sec = None
76+
77+
return results
78+
79+
80+
def make_comparison_table(all_results):
81+
# Map: framework -> { "hello": {...}, "order": {...}, "async_hello": {...}, "async_order": {...} }
82+
framework_map = {
83+
"aiohttp": "aiohttp",
84+
"aiozmq": "aiozmq",
85+
"blacksheep": "blacksheep",
86+
"fastapi": "fastApi",
87+
"sanic": "sanic",
88+
"zero": "zero(sync)",
89+
"zero-async": "zero(async)",
90+
}
91+
# Prepare a dict to collect results
92+
table_data = {fw: {} for fw in framework_map.values()}
93+
94+
# Helper to match test names to columns
95+
def classify(framework, test):
96+
if framework == "zero":
97+
if test == "hello":
98+
return "zero(sync)", "hello"
99+
elif test == "order":
100+
return "zero(sync)", "order"
101+
elif test == "async_hello":
102+
return "zero(async)", "hello"
103+
elif test == "async_order":
104+
return "zero(async)", "order"
105+
else:
106+
if test == "hello":
107+
return framework_map[framework], "hello"
108+
elif test == "order":
109+
return framework_map[framework], "order"
110+
return None, None
111+
112+
for benchmark, results in all_results.items():
113+
framework = benchmark.replace("benchmark-", "")
114+
for result in results:
115+
fw, col = classify(framework, result["test"])
116+
if fw and col:
117+
table_data[fw][col] = result
118+
119+
header = (
120+
'| Framework | "hello world" (req/s) | 99% latency (ms) | redis save (req/s) | 99% latency (ms) |\n'
121+
"| ----------- | --------------------- | ---------------- | ------------------ | ---------------- |"
122+
)
123+
rows = []
124+
for fw in [
125+
"aiohttp",
126+
"aiozmq",
127+
"blacksheep",
128+
"fastApi",
129+
"sanic",
130+
"zero(sync)",
131+
"zero(async)",
132+
]:
133+
hello = table_data[fw].get("hello", {})
134+
order = table_data[fw].get("order", {})
135+
row = (
136+
f"| {fw:<11} | "
137+
f"{hello.get('requests_per_sec', ''):<21} | {hello.get('99%_latency_ms', ''):<16} | "
138+
f"{order.get('requests_per_sec', ''):<18} | {order.get('99%_latency_ms', ''):<16} |"
139+
)
140+
rows.append(row)
141+
return header + "\n" + "\n".join(rows)
142+
143+
144+
if __name__ == "__main__":
145+
history_files = get_latest_history()
146+
all_results = {}
147+
for history_file in history_files:
148+
filepath = os.path.join("dockerize/history", history_file)
149+
if os.path.exists(filepath):
150+
benchmark_name = history_file.split("__")[0]
151+
results = parse_benchmark_log(filepath)
152+
all_results[benchmark_name] = results
153+
comparison_table = make_comparison_table(all_results)
154+
print(comparison_table)

benchmarks/dockerize/Makefile

Lines changed: 52 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,64 @@
11
SHELL := /bin/bash
22
benchmark-aiohttp:
3-
cd aiohttp && ( \
4-
docker-compose up -d --build gateway server redis; \
5-
sleep 2; \
6-
docker-compose up wrk-hello; \
7-
docker-compose up wrk-order; \
8-
docker-compose down; \
9-
)
3+
cd aiohttp && ( \
4+
docker-compose up -d --build gateway server redis; \
5+
sleep 2; \
6+
docker-compose up wrk-hello >> ../history/benchmark-aiohttp__$(shell date +%Y-%m-%d).log 2>&1; \
7+
docker-compose up wrk-order >> ../history/benchmark-aiohttp__$(shell date +%Y-%m-%d).log 2>&1; \
8+
docker-compose down; \
9+
)
1010

1111
benchmark-blacksheep:
12-
cd blacksheep && ( \
13-
docker-compose up -d --build gateway server redis; \
14-
sleep 2; \
15-
docker-compose up wrk-hello; \
16-
docker-compose up wrk-order; \
17-
docker-compose down; \
18-
)
12+
cd blacksheep && ( \
13+
docker-compose up -d --build gateway server redis; \
14+
sleep 2; \
15+
docker-compose up wrk-hello >> ../history/benchmark-blacksheep__$(shell date +%Y-%m-%d).log 2>&1; \
16+
docker-compose up wrk-order >> ../history/benchmark-blacksheep__$(shell date +%Y-%m-%d).log 2>&1; \
17+
docker-compose down; \
18+
)
1919

2020
benchmark-aiozmq:
21-
cd aiozmq && ( \
22-
docker-compose up -d --build gateway server redis; \
23-
sleep 2; \
24-
docker-compose up wrk-hello; \
25-
docker-compose up wrk-order; \
26-
docker-compose down; \
27-
)
21+
cd aiozmq && ( \
22+
docker-compose up -d --build gateway server redis; \
23+
sleep 2; \
24+
docker-compose up wrk-hello >> ../history/benchmark-aiozmq__$(shell date +%Y-%m-%d).log 2>&1; \
25+
docker-compose up wrk-order >> ../history/benchmark-aiozmq__$(shell date +%Y-%m-%d).log 2>&1; \
26+
docker-compose down; \
27+
)
2828

2929
benchmark-fastapi:
30-
cd fast_api && ( \
31-
docker-compose up -d --build gateway server redis; \
32-
sleep 2; \
33-
docker-compose up wrk-hello; \
34-
docker-compose up wrk-order; \
35-
docker-compose down; \
36-
)
30+
cd fast_api && ( \
31+
docker-compose up -d --build gateway server redis; \
32+
sleep 2; \
33+
docker-compose up wrk-hello >> ../history/benchmark-fastapi__$(shell date +%Y-%m-%d).log 2>&1; \
34+
docker-compose up wrk-order >> ../history/benchmark-fastapi__$(shell date +%Y-%m-%d).log 2>&1; \
35+
docker-compose down; \
36+
)
3737

3838
benchmark-sanic:
39-
cd sanic && ( \
40-
docker-compose up -d --build gateway server redis; \
41-
sleep 2; \
42-
docker-compose up wrk-hello; \
43-
docker-compose up wrk-order; \
44-
docker-compose down; \
45-
)
39+
cd sanic && ( \
40+
docker-compose up -d --build gateway server redis; \
41+
sleep 2; \
42+
docker-compose up wrk-hello >> ../history/benchmark-sanic__$(shell date +%Y-%m-%d).log 2>&1; \
43+
docker-compose up wrk-order >> ../history/benchmark-sanic__$(shell date +%Y-%m-%d).log 2>&1; \
44+
docker-compose down; \
45+
)
4646

4747
benchmark-zero:
48-
cd zero && ( \
49-
docker-compose up -d --build gateway server redis; \
50-
sleep 2; \
51-
docker-compose up wrk-hello; \
52-
docker-compose up wrk-order; \
53-
docker-compose up wrk-hello-async; \
54-
docker-compose up wrk-order-async; \
55-
docker-compose down; \
56-
)
48+
cd zero && ( \
49+
docker-compose up -d --build gateway server redis; \
50+
sleep 2; \
51+
docker-compose up wrk-hello >> ../history/benchmark-zero__$(shell date +%Y-%m-%d).log 2>&1; \
52+
docker-compose up wrk-order >> ../history/benchmark-zero__$(shell date +%Y-%m-%d).log 2>&1; \
53+
docker-compose up wrk-hello-async >> ../history/benchmark-zero__$(shell date +%Y-%m-%d).log 2>&1; \
54+
docker-compose up wrk-order-async >> ../history/benchmark-zero__$(shell date +%Y-%m-%d).log 2>&1; \
55+
docker-compose down; \
56+
)
57+
58+
benchmark-all:
59+
$(MAKE) benchmark-aiohttp
60+
$(MAKE) benchmark-blacksheep
61+
$(MAKE) benchmark-aiozmq
62+
$(MAKE) benchmark-fastapi
63+
$(MAKE) benchmark-sanic
64+
$(MAKE) benchmark-zero

benchmarks/dockerize/README.md

Lines changed: 3 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -36,48 +36,11 @@ In the `docker-compose.yml` you can play with the `--workers` count of gateway a
3636

3737
If you know the `wrk` well, just play with the configs. If you are naive like me, threads are basically your cpu threads (or 2x cpu threads). If you increase them no harm done, but it won't perform well I guess and connections are basically number of concurrent requests distributed amond the threads. You have to figure this out, if you start increasing the number, at one point your results will be saturated and servers will start to drop connections. That's the maximum you can reach.
3838

39-
I have used 2x cpu threads so `-t 16` and 16x25 = 400 connections.
39+
I have used cpu threads so `-t 8` and 8x10 = 80 connections.
4040

41-
## Latest benchmark results
41+
## History
4242

43-
11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz, 4 cores, 8 threads, 12GB RAM
44-
45-
*(Sorted alphabetically)*
46-
47-
| Framework | "hello world" (req/s) | 99% latency (ms) | redis save (req/s) | 99% latency (ms) |
48-
| --------- | --------------------- | ---------------- | ------------------ | ---------------- |
49-
| aiohttp | 9553.16 | 25.48 | 5497.03 | 27.90 |
50-
| aiozmq | 13241.74 | 12.12 | 5087.68 | 21.59 |
51-
| fastApi | 6036.61 | 31.28 | 3648.11 | 50.76 |
52-
| sanic | 13195.99 | 20.04 | 7226.72 | 25.24 |
53-
| zero | 18867.00 | 11.48 | 12293.81 | 11.68 |
54-
55-
## Old benchmark results
56-
57-
Intel Core i3 10100, 4 cores, 8 threads, 16GB RAM, with docker limits **cpu 40% and memory 256m**
58-
59-
*(Sorted alphabetically)*
60-
61-
| Framework | "hello world" example | redis save example |
62-
| --------- | --------------------- | ------------------ |
63-
| aiohttp | 1,424.24 req/s | 256.15 req/s |
64-
| aiozmq | 1,840.40 req/s | 712.22 req/s |
65-
| fastApi | 980.42 req/s | 252.08 req/s |
66-
| sanic | 3,085.80 req/s | 547.02 req/s |
67-
| zero | 5,000.77 req/s | 784.51 req/s |
68-
69-
MacBook Pro (13-inch, M1, 2020), Apple M1, 8 cores (4 performance and 4 efficiency), 8 GB RAM
70-
71-
*(Sorted alphabetically)*
72-
73-
| Framework | "hello world" example | redis save example |
74-
| --------- | --------------------- | ------------------ |
75-
| aiohttp | 12,409.50 req/s | 6,161.43 req/s |
76-
| fastApi | 8,653.16 req/s | 5,727.53 req/s |
77-
| sanic | 22,644.41 req/s | 7,750.49 req/s |
78-
| zero | 15,853.92 req/s | 11,167.89 req/s |
79-
80-
More about MacBook benchmarks [here](https://github.com/Ananto30/zero/blob/main/benchmarks/others/mac-results.md)
43+
You can check the history of the benchmarks in the `history` folder. It contains the results of previous runs, which can be useful for comparison and analysis.
8144

8245
### Note
8346

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM python:3.11-slim
1+
FROM python:3.13-slim
22

33
COPY . .
44
RUN pip install -r requirements.txt

benchmarks/dockerize/aiohttp/docker-compose.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ services:
2525
- "6379:6379"
2626
wrk-hello:
2727
image: skandyla/wrk
28-
command: -t 8 -c 64 -d 15s --latency http://gateway:8000/hello
28+
command: -t 8 -c 80 -d 30s --latency http://gateway:8000/hello
2929
depends_on:
3030
- gateway
3131
wrk-order:
3232
image: skandyla/wrk
33-
command: -t 8 -c 64 -d 15s --latency http://gateway:8000/order
33+
command: -t 8 -c 80 -d 30s --latency http://gateway:8000/order
3434
depends_on:
3535
- gateway
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM python:3.11-slim
1+
FROM python:3.13-slim
22

33
COPY . .
44
RUN pip install -r requirements.txt

benchmarks/dockerize/aiozmq/docker-compose.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ services:
2525
- "6379:6379"
2626
wrk-hello:
2727
image: skandyla/wrk
28-
command: -t 8 -c 64 -d 15s --latency http://gateway:8000/hello
28+
command: -t 8 -c 80 -d 30s --latency http://gateway:8000/hello
2929
depends_on:
3030
- gateway
3131
wrk-order:
3232
image: skandyla/wrk
33-
command: -t 8 -c 64 -d 15s --latency http://gateway:8000/order
33+
command: -t 8 -c 80 -d 30s --latency http://gateway:8000/order
3434
depends_on:
3535
- gateway
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM python:3.11-slim
1+
FROM python:3.13-slim
22

33
COPY . .
44
RUN pip install -r requirements.txt

0 commit comments

Comments
 (0)