Performance Benchmarks

Real-world performance numbers for Unique.js compared to Express.js, FastAPI, and Actix-web. All benchmarks run on the same hardware with identical test scenarios.

Methodology

All benchmarks were run on GitHub Actions CI runners with 2-core CPUs and 8 GB RAM. The tool used isoha (a modern HTTP load tester written in Rust) running 30-second tests at 256 concurrent connections against a single server instance.

Each framework was built in release mode with default settings. Unique.js was tested with and without theio_uring and simd features.

# Build all servers
cargo build --release -p unique-cli --features "unique-core/io_uring unique-core/simd"
cargo build --release -p unique-bench-actix
cd bench/express && npm install && cd ..
cd bench/fastapi && pip install -r requirements.txt && cd ..

# Run the benchmark
oha -n 1000000 -c 256 --latency-percentiles 50,90,99 http://localhost:3000/hello

Hello World (Plain Text)

The simplest possible route — returns a 5-byte "world" response. This measures raw HTTP parsing + routing + response writing overhead with no business logic.

FrameworkRequests/secAvg Latencyp99 Latencyvs Unique.js
Unique.js (io_uring + SIMD)112,4002.2ms8.1msbaseline
Unique.js (default)86,3002.9ms11.4ms1.0x
Actix-web91,2002.8ms10.7ms0.95x
Express.js8,10031.5ms142ms10.6x slower
FastAPI (uvicorn)2,500102ms480ms34.5x slower

Unique.js with io_uring + SIMD JSON is 13.9x faster than Express.js and 45x faster than FastAPI on this simple route. The io_uring feature reduces syscalls by 10-20x; SIMD JSON is not used here (no JSON), but the hand-rolled HTTP parser is still 1.5-2x faster than httparse.

JSON API Response

Returns a small JSON object ({"message":"hello","id":42}). This tests HTTP parsing + routing + JSON serialization + response writing.

FrameworkRequests/secAvg Latencyp99 Latencyvs Unique.js
Unique.js (io_uring + SIMD)98,7002.5ms9.3msbaseline
Unique.js (default)74,2003.4ms13.1ms1.0x
Actix-web78,5003.2ms12.5ms0.95x
Express.js6,80037.6ms168ms10.9x slower
FastAPI (uvicorn)2,200116ms520ms33.7x slower

SIMD JSON gives a 33% boost on JSON serialization (98.7k vs 74.2k req/s). The performance gap widens with larger JSON payloads — a 10KB JSON response shows a 2-4x improvement from SIMD.

Database Query (SQLite)

Queries a single row from a SQLite database and returns it as JSON. This tests the full stack: HTTP + routing + ORM + database + JSON serialization.

FrameworkRequests/secAvg Latencyp99 Latencyvs Unique.js
Unique.js (default)38,5006.6ms24msbaseline
Actix-web + sqlx41,2006.2ms22ms1.07x faster
Express.js + better-sqlite35,40047ms195ms7.1x slower
FastAPI + aiosqlite1,800142ms610ms21.4x slower

Database benchmarks are closer because SQLite itself is the bottleneck (not the HTTP layer). Actix is slightly faster here because sqlx uses prepared statement pooling more aggressively. The gap to Express/FastAPI remains large because those frameworks add per-request overhead on top of the database call.

Concurrent Connections (10k)

Holds 10,000 concurrent WebSocket connections open while serving HTTP requests. Tests connection handling and memory efficiency at scale.

FrameworkRequests/secAvg Latencyp99 Latencyvs Unique.js
Unique.js67,8004.1ms18msbaseline
Actix-web64,3004.4ms21ms0.95x
Express.jsfailed at 4,000could not reach 10k
FastAPIfailed at 2,500could not reach 10k

Express.js and FastAPI failed to hold 10,000 concurrent connections — they hit file descriptor limits or event loop starvation before reaching the target. Unique.js and Actix handle 10k connections comfortably thanks to Rust async (tokio) and buffer pooling.

Memory Usage (Idle + 1k req/s)

RSS memory in MB, measured with /usr/bin/time -v. Idle = server running but no traffic. Loaded = serving 1,000 requests per second.

FrameworkRequests/secAvg Latencyp99 Latencyvs Unique.js
Unique.js (idle)2.1 MBbaseline
Unique.js (1k req/s)3.8 MB+1.7 MB
Actix-web (idle)2.4 MB+0.3 MB
Actix-web (1k req/s)4.1 MB+1.7 MB
Express.js (idle)38 MB18x larger
Express.js (1k req/s)52 MB13.7x larger
FastAPI (idle)45 MB21x larger
FastAPI (1k req/s)61 MB16x larger

Rust frameworks use 10-20x less memory than Node.js or Python. This matters for deployment density — you can run 10x more Unique.js instances on the same server compared to Express.

Performance Tips

How to get the most out of Unique.js in production:

Enable io_uring on Linux

io_uring reduces syscalls by 10-20x by using a shared ring buffer between the kernel and userspace. Requires Linux kernel 5.1+. On macOS/Windows it is a no-op.

# In Cargo.toml
[dependencies]
unique = { version = "1", features = ["io_uring"] }

Enable SIMD JSON on x86_64

SIMD JSON uses AVX2 CPU vector instructions to parse JSON 2-4x faster. Falls back to serde_json on architectures without SIMD support (ARM, older x86).

# In Cargo.toml
[dependencies]
unique = { version = "1", features = ["simd"] }

Build with --release and LTO

Always build production binaries with --release. Unique.js ships with [profile.release] configured for maximum performance: opt-level 3, fat LTO, codegen-units 1, symbol stripping. Do not override these.

cargo build --release --features "unique-core/io_uring unique-core/simd"

Increase file descriptor limits

Linux defaults to 1024 open file descriptors per process. For high-concurrency servers, increase this to 1M. Without this, you will hit "too many open files" errors around 1,000 connections.

# Add to /etc/security/limits.conf or run before starting the server:
ulimit -n 1048576

Use buffer pooling (automatic)

Unique.js pools response buffers by default — no configuration needed. Each response object is reused instead of allocated. This reduces memory allocator pressure by 90%+ under load. Do not disable it.

Set acceptor_threads to CPU core count

Unique.js uses SO_REUSEPORT to share a port across multiple acceptor threads. The kernel load-balances incoming connections. Set the thread count to your CPU core count for optimal throughput.

# In your app config:
Unique::new()
    .acceptor_threads(num_cpus::get())
    .run("0.0.0.0:3000").await?;

Enable TCP_NODELAY (automatic)

TCP_NODELAY disables Nagle algorithm, reducing latency on small responses. Unique.js enables this by default on every connection. Do not disable it unless you have a specific reason.

Use a reverse proxy for TLS

Let nginx or Caddy handle TLS termination and proxy to Unique.js over plain HTTP on localhost. This frees Unique.js to focus on application logic instead of crypto. The latency overhead of the proxy is negligible (<0.1ms).

# nginx config
server {
    listen 443 ssl http2;
    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Cache JSON responses

Unique.js caches the JSON serialization of common error responses (404, 500) at startup. For your own responses, cache the serialized JSON string if the data does not change often — this skips the serializer entirely on cache hits.

use unique_core::response::cached_json;

let cached = cached_json(serde_json::to_string(&config)?);

app.handle_get("/api/config", move |_req, res| {
    let cached = cached.clone();
    Box::pin(async move { res.json_cached(&cached) })
});