Performance
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.
HTTP
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 generator | Throughput | p50 | p99 |
|---|---|---|---|
| wrk (1 process) | 406,762 req/s | 156µs | 167µs |
| SuperJ WebClient (pipelined ×16) | 515,000 req/s | — | — |
| wrk ceiling (same box) | ~423,000 req/s | wrk is the limit, not the server | |
Ryzen 9 9950X3D · server on CPU 4, client on CPU 3 · loopback TCP · 100 keep-alive connections
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);
}
}
Compiled through LLVM to native code. Full speed from request one — no profiling, no deoptimization.
Arena memory is reclaimed deterministically. That's why the latency curve stays flat under load.
The response buffer is built once and rewound per request. Steady-state serving allocates nothing.
One event loop, one thread — no locks, no context switches, no cross-core cache traffic on the hot path.
superj_web
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 still beats nginx on the same box.
| Workload | superj_web | nginx | SDK reference |
|---|---|---|---|
| Small file (59 bytes) | 405,229 req/s | 218,948 req/s | 407,094 req/s |
| Large file (10MB) | 1,510 req/s (14.75 GB/s) | 1,066 req/s (10.41 GB/s) | — |
Ryzen 9 9950X3D · server on CPU 4, wrk on CPU 5 · loopback TCP · 3-run average
SEDA
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
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.
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.
| Metric | SuperJ | C++ hnswlib | Result |
|---|---|---|---|
| Search throughput | 24.8K q/s | 23.7K q/s | SuperJ ~5% faster |
| Insert throughput | ~25K v/s | 25.6K v/s | parity |
| Recall@1 | 100% | 100% | equal |
| Benchmark | Java | SuperJ | Speedup |
|---|---|---|---|
| InvertedIndexer.search(1024) TotalHits | 37 ns | 6 ns | 6.2× faster |
| InvertedIndexer.search(1) TotalHits | 35 ns | 3 ns | 11.7× faster |
| InvertedIndexer.build (100K records) | 755 ns | 118 ns | 6.4× faster |
| HashIndexer.search (100K lookups) | 22 ns | 17 ns | 1.3× faster |
SuperJ: enterprise SDK, -O3 (bounds check on, no SIMD). Java: JDK 17, cold JIT. macOS arm64.
| Operation | Java | SuperJ | Speedup |
|---|---|---|---|
| JWT.sign | 858 ns | 177 ns | 4.8× faster |
| JWT.verify | 801 ns | 181 ns | 4.4× faster |
| JWT.encryptAndSign | 1,238 ns | 364 ns | 3.4× faster |
| JWT.verifyAndDecrypt | 1,237 ns | 342 ns | 3.6× faster |
| Handler | ops/sec | ns/op |
|---|---|---|
| SearchHandler | 1,294,799 | 772 |
| GetRecordHandler | 1,301,241 | 768 |
| AddRecordHandler | 1,299,168 | 769 |
| LoginHandler | 1,303,672 | 767 |
| RecordJsonWriter | 3,433,949 | 291 |
NullWriter discards response bytes — pure handler + store + indexer + serialization. 3-5 rounds, averaged.
The ledger
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.
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.
Plain-object puts/gets at parity with C; up to 7× faster than tidwall C on adversarial keys; 4× faster on scattered int keys.
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.
3.4-11.7× faster on indexers and JWT — zero allocation on the hot path, native crypto intrinsics, no GC.
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.
1.11-1.17× slower — per-element pointer reload + bounds checks on a stride-3/4 pattern. --no-bounds-check closes most of the encode gap.
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.
BitSet scan, object field access, reverse-complement — the per-access checks C doesn't carry. --no-bounds-check closes most of it.
Reproduce
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.
# 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