Performance

Native speed, flat tails

AOT compilation to LLVM, zero-allocation hot paths, arena memory, and a single-threaded event loop put SuperJ in the same class as the fastest published frameworks — with a latency curve that stays flat because there's no collector to pause on.

515K
requests / sec — wrk caps at ~423K, so it's the bottleneck
~1.1×
p99 within ~1.1× of p50 — a flat tail
0
GC pauses — arena memory, no scans

HTTP

The fastest web server in the world

A web server on the built-in HTTP stack, compiled to a single native binary and load-tested over loopback. The server is pinned to one core, the load generator to another. It serves faster than wrk can issue requests.

Load generatorThroughputp50p99
wrk (1 process)406,762 req/s156µs167µs
SuperJ WebClient (pipelined ×16)515,000 req/s
wrk ceiling (same box)~423,000 req/swrk is the limit, not the server

Ryzen 9 9950X3D · server on CPU 4, client on CPU 3 · loopback TCP · 100 keep-alive connections

WebServer.sj
class HelloHandler implements HttpRequestHandler {
  private final ByteArray response;
  HelloHandler() {
    String body = "Hello from SuperJ!\n";
    this.response = new ByteArray(
      "HTTP/1.1 200 OK\r\nContent-Length: "
      + body.length() + "\r\n\r\n" + body);
  }
  public void handle(HttpRequest r, SessionWriter w) {
    this.response.rewind();  // reuse — zero alloc
    w.write(this.response);
  }
}
  • aot

    No JIT warm-up

    Compiled through LLVM to native code. Full speed from request one — no profiling, no deoptimization.

  • no gc

    No pauses

    Arena memory is reclaimed deterministically. That's why the latency curve stays flat under load.

  • zero-alloc

    Allocation-free hot path

    The response buffer is built once and rewound per request. Steady-state serving allocates nothing.

  • one loop

    No lock traffic

    One event loop, one thread — no locks, no context switches, no cross-core cache traffic on the hot path.

superj_web

The edge server — 1.85× faster than nginx, up to 4.8× faster than Caddy

The same stack, wrapped in a full edge web server with TLS termination, routing, a static file cache, and reverse proxy. Despite the added overhead it beats nginx and Caddy on the same box, on every file size, for both static serving and reverse proxy.

Static file serving — superj_web vs nginx vs Caddy

Workloadsuperj_webnginxCaddySDK reference
Small file (59 bytes)405,229 req/s218,948 req/s70,248 req/s (128B)407,094 req/s
1 KB286,710 req/s59,974 req/s
16 KB254,602 req/s59,479 req/s
64 KB146,959 req/s56,634 req/s
Large file (10MB / 1MB)1,510 req/s (14.75 GB/s) / 15,229 req/s1,066 req/s (10.41 GB/s)10,971 req/s (1MB)

Ryzen 9 9950X3D · server on one core, wrk on another · loopback TCP · 3-run average. Caddy v2.8.4, superj_web built with --release --enterprise. nginx: 1.24.0, single worker, sendfile on, access_log off.

Reverse proxy — superj_web vs Caddy (front → back, isolated CPUs)

FileCaddy req/ssuperj_web req/sSpeedup
128B34,677106,8173.1× faster
1 KB33,008106,6223.2× faster
16 KB24,45688,6053.6× faster
64 KB20,02243,2422.2× faster

100 keep-alive conns · front on CPU 14, back on CPU 15 · 128 pooled keep-alive conns per upstream · raw response forwarding (no rebuild).

Why it wins. The static hot path is allocation-free: pre-built responses, rewind() reuse, an in-memory file cache with lazy mtime invalidation. The proxy hot path adds connection pooling (128 keep-alive conns per upstream) and raw response forwarding with prepareForReuse() on the reader — no per-request allocations on either path. The single-threaded event loop handles 288K req/s (static 128B) and 107K req/s (proxy 128B) on one core; the gaps to nginx/Caddy are algorithmic, not threading.

SEDA

Cross-process coordination — 88 ns/round

The SEDA sequencer is the total-order event stream that replaces shared mutable state. In-process on an M4 Max it measured 88 ns/round — faster than raw cross-process mmap spin (152 ns/round), because it never crosses cores and pays no cache-coherence tax.

Application — xpe_db

A real database, not just kernels

xpe_db is an embedded document database with an HNSW vector index, an inverted index, a hash index, and a JWT/auth subsystem — ported from Java to SuperJ. The binary runs with no JVM, no GC, no JIT warm-up. These are application-level numbers, the kind a user actually feels.

HNSW vector index — SuperJ vs C++ hnswlib

Byte-quantized (uint8) L2 distance, M=16, maxM0=32, ef=200, k=10, dims=128, n=1000. Same seeded RNG, same workload. Precise 20K-query nanoTime bench.

MetricSuperJC++ hnswlibResult
Search throughput24.8K q/s23.7K q/sSuperJ ~5% faster
Insert throughput~25K v/s25.6K v/sparity
Recall@1100%100%equal

Inverted & hash indexers — SuperJ vs Java

BenchmarkJavaSuperJSpeedup
InvertedIndexer.search(1024) TotalHits37 ns6 ns6.2× faster
InvertedIndexer.search(1) TotalHits35 ns3 ns11.7× faster
InvertedIndexer.build (100K records)755 ns118 ns6.4× faster
HashIndexer.search (100K lookups)22 ns17 ns1.3× faster

SuperJ: enterprise SDK, -O3 (bounds check on, no SIMD). Java: JDK 17, cold JIT. macOS arm64.

JWT / auth — SuperJ vs Java

OperationJavaSuperJSpeedup
JWT.sign858 ns177 ns4.8× faster
JWT.verify801 ns181 ns4.4× faster
JWT.encryptAndSign1,238 ns364 ns3.4× faster
JWT.verifyAndDecrypt1,237 ns342 ns3.6× faster

Case study — superK

superK vs kdb+ — 13 of 14 wins, up to 84×

superK is a time-series database built from the ground up in SuperJ — fully compatible with the q language and the q-IPC wire protocol: a q client that connects to kdb+ connects to superK without modification, and a q script that runs on kdb+ runs on superK. Same query language, same data model, same client protocol. No JVM, no JIT, no garbage collector; single-threaded by design, scaled with processes, not threads.

The benchmark. The paper "Benchmarking Specialized Databases for High-frequency Data" (arXiv:2301.12561) defines 14 query benchmarks over cryptocurrency exchange data — trades and order book — covering volume aggregation, VWAP, market depth, bid-ask spread, NBBO, log returns, and volatility. It was designed to compare kdb+ against ClickHouse, InfluxDB, and TimescaleDB. kdb+ won. We use the same 14 benchmarks, the same data shape, the same queries — both engines on the same machine, same core, same data, timed the same way. paper_verify.q proves the two databases agree value by value before any timing is quoted; both engines run the same q text through their own REPL, so the clock covers the full path a user hits (parse, evaluate, format, print), not just an isolated kernel.

IDQuerysuperK (µs)kdb+ (µs)WinnerRatio
T-V1Trade volume, by symbol7,49514,303superK1.9×
T-V2Trade volume, by symbol × exchange227,712222,269kdb+1.02×
T-VWAPVolume-weighted average price2,0891,794superK1.2×
O-TOrder-book top-of-book4.8402superK84×
O-B1Best bid1,1112,556superK2.3×
O-B2Best bid & ask5,25614,208superK2.7×
O-SSpread709827superK1.2×
O-V1Order-book volume, by symbol40,29461,012superK1.5×
O-V2Order-book volume, by symbol × exchange164,769513,127superK3.1×
O-NBBONational best bid & offer420859superK2.0×
C-RLog returns5,5715,774superK1.04×
C-VTVolatility, trades2,8683,048superK1.06×
C-VO1Volatility, order book (bid)5,5225,735superK1.04×
C-VO2Volatility, order book (bid & ask)32,09737,053superK1.2×

AMD Ryzen 9 9950X3D · 30 GB RAM · NVMe · both engines taskset -c 2 (isolated core), warm cache · 10 iterations, mean · data: 30 partitions + 1 NBBO day, ~20M trades rows, ~33M order_book rows, 39 GB, 3 symbols / 2 sides / 5 exchanges · superK commit 2c84085, superj build --release (AVX2, no bounds check, -O3) · kdb+ 5.0

superK wins 13 of 14. The one remaining loss — T-V2 at 1.02× — is within measurement noise; the two engines are effectively tied. The wins range from 1.04× (C-R) to 84× (O-T). The win comes from the storage and query engine itself, not a different data shape: superK implements the same splayed, parted, p#-indexed columnar model as kdb+, AOT-compiled through LLVM with zero allocation on the hot path.

The ledger

SuperJ vs C — the full picture

We ran 54 head-to-head benchmarks against C (and where relevant, C++ simdjson/hnswlib, Rust, Java) on the same machine, the same workload, the same iteration count. Each bar below splits at the speed ratio: amber is SuperJ, blue is C. A 50/50 split is a tie; if SuperJ is 2× faster, its segment is twice as long. We show every benchmark, including the ones we lose.

11 wins 31 ties 12 losses out of 54 benchmarks — 42 (78%) are wins or ties
Amber = SuperJ · Blue = C · longer segment = faster · ratio shown is speedup of the faster side
Arithmetic loop
tie
Array scan int[]
tie
String concat (chain)
1.63x
String valueOf (int/long)
1.25x
String charAt (ASCII)
tie
HashMap put (Object)
tie
HashMap get (Object)
tie
Object alloc
tie
Object field access
1.25x
Method call (recursive fib)
tie
UDP networking
tie
SWAR parseInt (LTO)
1.35x
HashMap get scattered vs C
4.00x
HashMap get stride-256 vs C
7.17x
SHA-256 (64B, hardware)
tie
BitSet set seq (LTO)
2.04x
BitSet get seq (LTO)
1.30x
BitSet scan (LTO)
1.70x
JSON small 7B vs simdjson
1.24x
JSON array 22B vs simdjson
1.08x
JSON object 77B vs simdjson
1.13x
JSON big 5.7KB vs simdjson
1.09x
i128 add/mul/div
tie
i256 add/mul
tie
i256 div (general)
3.35x
i512 add/mul
tie
i512 div (general)
10.42x
i128 div by const
tie
i128 array scan
tie
i256 array scan
tie
i512 array scan
tie
secp256k1 keygen
tie
secp256k1 sign
tie
secp256k1 verify
tie
secp256k1 recover
tie
regex-redux vs Rust
tie
fannkuch-redux
1.03x
spectral-norm
1.27x
mandelbrot
tie
reverse-complement
1.76x
k-nucleotide
1.75x
n-body
tie
fasta
1.05x
Primes (sieve+trie)
1.06x
Base64 encode
tie
Base64 decode (no-bc)
1.10x
Brainfuck
tie
matmul N=64 SIMD-tile
2.57x
matmul N=128 SIMD-tile
2.02x
matmul N=512 SIMD-tile
1.15x
HNSW search vs hnswlib
tie
HNSW insert vs hnswlib
tie
Distance fn 64-dim
tie
Distance fn 512-dim
tie
SuperJ C / C++ / Rust 54 benchmarks · ratio = speedup of the faster side

How the bars are calculated. Each benchmark pits SuperJ against C (or C++ simdjson / C++ hnswlib / Rust, where noted) on the same machine, the same workload, the same iteration count. SuperJ is compiled with clang -O3 linking; C with clang -O3 (g++ -O3 -mcpu=native for the C++ benchmarks). Best-of-3 warm runs. The split point of each bar is the speed ratio: if SuperJ is r× the time of C, SuperJ's segment is 1/(1+r) and C's is r/(1+r) — so a 2× win gives SuperJ a 2:1 segment, a tie is 50/50, and a loss shrinks SuperJ's segment. A win is >5% faster, a tie is within ±5%, a loss is >5% slower. No benchmark is cherry-picked — the 54 suites cover arithmetic, array scans, string ops, object alloc/access, method dispatch, networking, integer parsing, hash maps (Object keys, 3 key distributions), SHA-256, BitSet, JSON (4 document sizes), wide-integer arithmetic (i128/i256/i512), secp256k1 ECDSA, 8 Benchmarksgame ports, Base64, Brainfuck, SIMD matrix multiply, and a full HNSW vector index. The complete result table and methodology live in benchmarks/RESULTS.md and doc/hnsw_performance.md in the source tree.

Where SuperJ wins

  • compute

    Hash maps

    Plain-object puts/gets at parity with C; up to 7× faster than tidwall C on adversarial keys; 4× faster on scattered int keys.

  • numerics

    Wide-integer division & SIMD matmul

    i256/i512 general division 3-10× faster than C; cache-blocked SIMD-tile GEMM beats auto-vectorized C by 1.15-2.57× at every matrix size.

  • application

    vs Java

    3.4-11.7× faster on indexers and JWT — zero allocation on the hot path, native crypto intrinsics, no GC.

Where C still wins

  • gap

    String concat & valueOf

    1.25-1.63× slower on allocation-bound paths — SuperJ allocates a real heap String; C writes a throwaway stack buffer. charAt is at parity.

  • gap

    Base64 decode

    1.10× slower — per-element pointer reload + bounds checks on a stride-3/4 pattern. Encode is now at parity after #2376. --no-bounds-check closes most of the remaining gap.

  • gap

    JSON object (77B) vs simdjson

    1.13× slower — the per-byte scalar tail on docs under 64B. SuperJ is faster than simdjson on 7B and 22B docs, and within 9% on 5.7KB.

  • gap

    Bounds-checked random access

    BitSet scan, object field access, reverse-complement — the per-access checks C doesn't carry. --no-bounds-check closes most of it.

Reproduce

Run the benchmarks yourself

The HTTP server and load generator ship with SuperJ at demo/sj/demo/WebServer.sj and demo/sj/demo/WebClient.sj. The edge server benchmark script is in the superj_web repo.

terminal
# HTTP server + wrk
superj compile "$SJ_HOME/demo/sj/demo/WebServer.sj" --sdk-path "$SJ_HOME/sdk" --link --output webserver
./webserver 8080 &
wrk -t2 -c100 -d30s --latency http://127.0.0.1:8080/

# pipelined client (when wrk is the bottleneck)
superj compile "$SJ_HOME/demo/sj/demo/WebClient.sj" --sdk-path "$SJ_HOME/sdk" --link --output webclient
./webclient 8080 64 16 15

# scale across cores — same binary, more copies
for i in 1 2 3 4; do ./webserver 8080 & done