PlaintextGET /plaintext answers 13 bytes over kept-alive connections: the cost of HTTP itself.
roux98,191
fourneau-zig75,361
axum71,012
go46,741
basic-webserver25,214
table
competitor
req/s (median)
p95 ms
p99 ms
p99.9 ms
rounds
spread %
server CPU %
loader CPU %
Mbit/s in / out
limited by
steal %
peak RSS
roux
98,191
5.22
7.43
11.41
115,163 · 87,941 · 98,191
27.7
64.2
41.7
121 / 154
connections (nothing saturated)
0.1
112.5 MiB
fourneau-zig
75,361
7.05
9.96
14.42
75,361 · 84,258 · 70,398
18.4
49.2
29.2
93 / 118
connections (nothing saturated)
0.1
114.9 MiB
axum
71,012
7.12
9.91
14.37
80,908 · 71,012 · 69,293
16.4
78.3
29.6
88 / 111
connections (nothing saturated)
0.0
16.1 MiB
go
46,741
10.61
13.93
19.27
44,295 · 47,998 · 46,741
7.9
90.8
22.8
58 / 73
server CPU
0.0
23.8 MiB
basic-webserver
25,214
17.56
20.31
25.30
25,567 · 24,489 · 25,214
4.3
96.4
16.3
31 / 40
server CPU
0.0
27.8 MiB
Echo 4 KiBPOST /echo with a 4 KiB body, answered with the same bytes: reading bodies and writing larger responses.
roux27,083
fourneau-zig25,198
axum22,133
go20,144
basic-webserver17,843
table
competitor
req/s (median)
p95 ms
p99 ms
p99.9 ms
rounds
spread %
server CPU %
loader CPU %
Mbit/s in / out
limited by
steal %
peak RSS
roux
27,083
15.63
20.58
38.32
27,083 · 18,933 · 31,712
47.2
55.2
25.5
972 / 957
connections (nothing saturated)
0.1
115.3 MiB
fourneau-zig
25,198
16.71
20.75
26.25
19,097 · 26,326 · 25,198
28.7
49.5
23.3
909 / 891
connections (nothing saturated)
0.2
115.1 MiB
axum
22,133
18.51
23.46
29.92
25,716 · 22,133 · 21,817
17.6
58.4
21.2
800 / 787
connections (nothing saturated)
0.1
15.7 MiB
go
20,144
21.48
28.76
225.42
18,254 · 21,472 · 20,144
16.0
84.9
22.4
740 / 733
connections (nothing saturated)
0.0
23.8 MiB
basic-webserver
17,843
22.16
28.24
49.96
17,088 · 18,364 · 17,843
7.2
93.9
19.3
659 / 652
server CPU
0.0
31.8 MiB
TemplatesGET /menu renders a 12-row HTML table from a template per request, every value escaped: the work of a server-rendered page.
roux82,936
fourneau-zig74,974
axum69,279
go18,270
basic-webserver6,504
table
competitor
req/s (median)
p95 ms
p99 ms
p99.9 ms
rounds
spread %
server CPU %
loader CPU %
Mbit/s in / out
limited by
steal %
peak RSS
roux
82,936
6.08
8.55
12.16
75,152 · 84,394 · 82,936
11.1
78.5
34.2
99 / 614
connections (nothing saturated)
0.1
115.5 MiB
fourneau-zig
74,974
7.15
10.17
15.74
61,894 · 78,179 · 74,974
21.7
57.7
30.2
89 / 555
connections (nothing saturated)
0.1
115.0 MiB
axum
69,279
7.12
10.06
14.63
74,120 · 58,783 · 69,279
22.1
81.6
30.0
83 / 517
connections (nothing saturated)
0.0
17.2 MiB
go
18,270
25.92
32.68
44.19
18,270 · 18,891 · 18,201
3.8
97.6
13.3
22 / 135
server CPU
0.0
23.8 MiB
basic-webserver
6,504
60.67
69.13
86.20
6,519 · 6,128 · 6,504
6.0
99.2
8.3
8 / 50
server CPU
0.0
31.8 MiB
SSE (Datastar)GET /sse with Datastar's signals in the query, answered with a stream of ten Datastar events, a chunk each: the work of a Datastar action.
fourneau-zig60,358
axum47,810
roux45,386
go17,507
basic-webserver4,064
table
competitor
req/s (median)
p95 ms
p99 ms
p99.9 ms
rounds
spread %
server CPU %
loader CPU %
Mbit/s in / out
limited by
steal %
peak RSS
fourneau-zig
60,358
9.19
13.17
17.98
60,912 · 55,369 · 60,358
9.2
49.3
33.5
87 / 609
connections (nothing saturated)
0.0
115.0 MiB
axum
47,810
9.48
12.82
17.62
53,393 · 42,285 · 47,810
23.2
89.9
31.1
69 / 482
connections (nothing saturated)
0.0
16.9 MiB
roux
45,386
9.87
13.38
18.46
45,386 · 47,638 · 43,781
8.5
85.3
26.2
65 / 458
connections (nothing saturated)
0.1
115.5 MiB
go
17,507
26.87
35.23
48.82
17,507 · 17,852 · 16,565
7.3
97.8
21.2
39 / 216
server CPU
0.0
23.6 MiB
basic-webserver
4,064
90.49
108.43
141.16
4,138 · 3,919 · 4,064
5.4
99.7
15.3
17 / 63
server CPU
0.0
31.8 MiB
Connection churnGET /plaintext on a new connection every request: accepting, and closing, as fast as possible.
fourneau-zig13,976
roux13,645
axum13,517
go12,447
basic-webserver7,895
table
competitor
req/s (median)
p95 ms
p99 ms
p99.9 ms
rounds
spread %
server CPU %
loader CPU %
Mbit/s in / out
limited by
steal %
peak RSS
fourneau-zig
13,976
8.79
11.84
16.05
13,742 · 14,958 · 13,976
8.7
47.9
28.0
51 / 55
connections (nothing saturated)
0.1
115.1 MiB
roux
13,645
9.02
12.22
16.33
16,308 · 12,368 · 13,645
28.9
44.6
26.6
50 / 53
connections (nothing saturated)
0.0
115.5 MiB
axum
13,517
8.94
12.01
16.15
16,194 · 13,285 · 13,517
21.5
62.8
27.6
49 / 53
connections (nothing saturated)
0.0
16.9 MiB
go
12,447
9.35
12.45
17.70
11,129 · 13,521 · 12,447
19.2
75.5
26.6
45 / 49
connections (nothing saturated)
0.1
23.8 MiB
basic-webserver
7,895
12.90
16.53
23.50
7,895 · 7,444 · 8,374
11.8
80.1
18.1
28 / 31
connections (nothing saturated)
0.0
28.8 MiB
Under load: Templatesp99 latency against the offered rate (open loop: requests arrive at a fixed rate whatever the server does). Each server is offered 50% to 120% of its own maximum above.
offered requests per second →
axum
go
basic-webserver
roux
fourneau-zig
the loader was the limit
Under load: RealWorld (Conduit)The mean latency of every request against the offered rate: the article list (50%), an article (30%), a comment (15%) and a favorite (5%) at once, open loop, at the same rates for every server, each climbing until it falls behind.
offered requests per second →
fourneau-zig
basic-webserver
axum
go
roux
the loader was the limit
How it runs
A race runs at 03:00 New York time when fourneau, roux or this repository has a new commit. Each result names the commits it raced.
Each server class gets fresh DigitalOcean droplets: the server on one, the load generator (oha) on a bigger one, over the private network.
Five workloads raced flat out: plaintext, a 4 KiB echo, a templated HTML page, a Datastar event stream, and a new connection per request. Three rounds in random order; the median counts, of throughput and of each latency percentile.
And one with a database: a slice of RealWorld’s Conduit API on SQLite, reads and writes at once, offered at the same rising rates to every server.