Liking cljdoc? Tell your friends :D

Performance

Loopback bench on an M-series laptop, JDK 25. All servers boot in the same JVM, sharing cores with the load generator.

HTTP/1.1 throughput

wrk against a plain 404 responder.

WorkloadEnsōhttp-kitJettyAleph
non-pipelined, -c64127.8k123.8k110.5k85.8k
pipelined depth 161.88M523k232k71k
pipelined depth 645.13M550k244k73k

Ensō leads across the board, especially on pipelined workloads.

HTTP/2 throughput

Localhost h2load over TLS, 5-byte body, self-signed cert.

Configrps
-c 32 -m 32661k
-c 16 -m 64788k
-c 8 -m 128916k
-c 4 -m 256849k

10-run distribution at -c 8 -m 128, 500k requests each: min 763k, median 894k, best 907k.

HTTP/3 throughput

Localhost quiche-client fanout, 64 concurrent QUIC connections × 5000 streams each = 320,000 requests total. Self-signed cert, plaintext 200 response. Same JVM, same host, all three servers boot in-process.

Serverrpswall (ms)
Ensō53,2356011
Netty h3 (incubator 0.0.28, native quic 0.0.66, BoringSSL, vendored quiche master)52,2536124
Jetty h3 (12.0.14, JNA quiche)2,766115,707

Ensō ~2% ahead of Netty (both use libquiche + JNI), ~19× Jetty (JNA path). 0 failures across all runs.

Allocation

Sampled via clj-async-profiler :event :alloc at default rate (~1 MB TLAB fill per sample). Lower is better.

HTTP/1.1 vs http-kit

WorkloadEnsō samples/reqhttp-kit samples/reqRatio
non-pipelined0.00130.01027.8× less
pipelined d=640.000720.010414.3× less

HTTP/2

h2load -c 8 -m 128, 500k requests over TLS.

Ensō samples/req≈ bytes/req
0.0042~4.2 KB

Extra HTTP/2 cost vs HTTP/1.1: HPACK HeaderField per decoded header, one Http2Stream per request, per-frame byte[] for the writer queue, TLS record scratch. Wire bytes drop ~50% vs HTTP/1.1 for the same responses thanks to HPACK indexed emission.

HTTP/3

quiche-client fanout -c 32 × 2000, 64k requests.

Ensō samples/req≈ bytes/req
0.0044~4.4 KB

Profile dominated by one VirtualThread per request, Ring PersistentArrayMap for headers, per-connection QPACK decode String[] pairs. Zero allocation in the recv path (H3FrameReader.feed(byte[], off, len)); QPACK encode folds directly into the outbound frame scratch buffer; a single stream_send per response (HEADERS + DATA concatenated).

Reproduce

clojure -M:bench      # starts nREPL

Then in the REPL:

(require 'enso.bench)
(enso.bench/start!)
(enso.bench/compare! {:duration "10s" :depth 64})
(enso.bench/profile-alloc! "http://127.0.0.1:8080/nope" {:duration "10s" :depth 64})

;; HTTP/3 comparison across all three servers
(enso.bench/compare-h3! {:clients 32 :per-client 1000})

Can you improve this documentation?Edit on GitHub

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close