Skip to main content
Version: Next (unreleased)

HTTP

All HTTP results are from an Apple M4 (10 cores, 4 of them performance cores, macOS 15.5) with the load generator oha 1.16.0 on the same machine, Node v26.12.0 and Bun 1.4.4. Each server is one process with one event loop unless a table says otherwise. There are no Linux HTTP results yet.

Hello server (7 Nov 2026)​

64 keep-alive connections, 5 s per route. RSS is sampled right after the load; startup is the time from spawn to the first 200 on /ping. Source: bench/results/2026-11-07-Apple-M4-http.csv.

serverreq/sp50p99RSS after loadstartupexecutable
xisto231,0970.232 ms0.507 ms4.20 MB2 ms111,088 B
Bun198,2230.271 ms0.587 ms34.47 MB2 ms88,660,864 B
Node136,9130.398 ms0.865 ms100.72 MB11 ms61,220,224 B

The same client program (bench/http/client.ts, 20,000 sequential requests against the xisto server) peaks at 1.91 MB on xisto, 35.20 MB on Bun and 220.30 MB on Node.

Todo API (5 Nov 2026)​

A JSON API seeded with 50 todos; 64 connections, 10 s per route. list serializes all 50 items, read returns one, update parses a JSON body. Source: bench/results/2026-11-05-Apple-M4-todo.csv.

routexistoBunNode
list111,914 req/s, p99 1.202 ms, 3.80 MB115,425 req/s, p99 1.121 ms, 34.38 MB68,697 req/s, p99 1.904 ms, 78.78 MB
read142,231 req/s, p99 0.866 ms, 3.72 MB147,135 req/s, p99 0.852 ms, 34.34 MB78,439 req/s, p99 1.728 ms, 83.08 MB
update130,609 req/s, p99 0.959 ms, 5.05 MB131,247 req/s, p99 0.961 ms, 39.89 MB79,869 req/s, p99 1.692 ms, 108.39 MB

Bun is slightly ahead on throughput on all three routes (0.5% to 3.5%), with 8 to 9 times xisto's memory. Startup: xisto 1 ms, Bun 2 ms, Node 10 ms.

Conduit: a NestJS app (8 Nov 2026)​

Conduit is the public RealWorld example built with NestJS: controllers, guards, JWT, argon2, a Postgres database and a Redis cache. The same source was built with xisto and run on Node v26.12.0 (after npm run build). 20 connections, 30 s per route, startup the median of 5 fresh starts, cold latency the median of 10. Source: bench/results/conduit-mac-2026-11-08.txt.

xistoNode
startup (to the first 200)21 ms268 ms
RSS at rest2.6 MB136.0 MB
RSS after the load4.8 MB147.5 MB

Under load, with the Redis cache warm:

routexisto req/sNode req/sxisto p99Node p99xisto CPU per 1k reqNode CPU per 1k reqxisto RSSNode RSS
article list (2 queries)15,93715,9423.10 ms3.55 ms39.1 ms48.1 ms5.9 MB162.7 MB
article by slug30,01128,4701.96 ms1.71 ms12.7 ms22.7 ms4.3 MB137.1 MB
current user (guard, JWT, query)12,7027,3422.42 ms4.55 ms41.1 ms135.1 ms5.4 MB157.3 MB
tags26,05419,5241.32 ms1.71 ms19.3 ms41.6 ms4.2 MB132.2 MB
login (argon2)6150555.61 ms456.10 ms132,489 ms180,128 ms197.2 MB387.5 MB

Without the cache (Redis stopped; every request reads Postgres):

routexisto req/sNode req/sxisto CPU per 1k reqNode CPU per 1k reqxisto RSSNode RSS
article list7,4367,086104.0 ms121.6 ms5.7 MB153.2 MB
article by slug11,9539,23137.6 ms80.3 ms4.8 MB147.5 MB

The article list ties on throughput: the database is the limit, and xisto spends less CPU per request. Node has the better p99 on the article by slug (1.71 against 1.96 ms) and on login (456 against 556 ms). Login is argon2 hashing: both use about 8 cores (8.05 and 8.93), and xisto's p50 is 325 ms against Node's 401 ms.

Single-request latency, cold (cache flushed) / warm, in ms:

routexistoNode
article list2.74 / 0.452.54 / 0.56
article by slug1.44 / 0.452.06 / 0.55
current user1.06 / 0.531.85 / 0.88
tags0.64 / 0.341.12 / 0.45
login26.01 / 22.0930.95 / 29.35

Threads​

xisto rows only: the script also runs Node (cluster) and Bun (reusePort), but the versioned result holds xisto alone. 256 connections, 10 s per route, the load generator on the same machine. XISTO_WORKERS=N runs N event loops on one port. Source: bench/results/2026-11-05-Apple-M4-threads.csv (5 Nov 2026).

workers/ping req/sp99CPU s per 100k requestsRSS
1229,2752.324 ms0.4326.0 MB
2217,9322.585 ms0.8946.1 MB
4213,2823.970 ms1.4206.2 MB
8212,7854.039 ms1.6126.4 MB

One event loop serves the most requests with the least CPU. More workers add CPU per request without adding throughput on this machine, where the load generator competes for the same cores. Threads pay off for CPU-bound work (see Benchmarks Game), not for I/O.

Resilience​

xisto only (one worker, Todo API). Source: bench/results/2026-11-05-Apple-M4-resilience.csv (5 Nov 2026).

phasereq/sp99connection errorsRSSalive after
64 connections227,3020.508 ms04.7 MB200
1,024 connections218,3205.326 ms027.3 MB200
4,096 connections200,88814.355 ms84348.6 MB200
overload, 4,096 for 10 s205,04824.817 ms1,06943.6 MB200
invalid JSON storm (400 expected)198,8012.531 ms041.5 MB200
1,000 idle + 64 active203,1030.598 ms045.1 MB200
60 s soak151,7030.840 ms04.1 MB (34.8 before)200
recovery, 64 connections159,8970.781 ms05.5 MB200

The server answered 200 after every phase, and memory returned to 4.1 MB after the soak.

Slow dependencies​

xisto only (one worker), 64 connections, a fake database answering after a fixed delay. one makes one call per request, three makes three in sequence. Source: bench/results/2026-11-05-Apple-M4-deps.csv (5 Nov 2026).

routedelayreq/sp99RSS
one1 ms55,5571.5 ms13.8 MB
one100 ms633103.9 ms21.1 MB
one5,000 ms12.85,007.0 ms13.5 MB
three100 ms211313.5 ms21.0 MB

At 100 ms per call, 64 connections give at most 640 req/s: the server reaches 633 with a p99 of 103.9 ms, so the waiting requests cost no throughput.

See also​