GioJSdocs

Benchmarks

GioJS keeps memory flat under sustained load because Rust owns the HTTP layer - cache hits never allocate in Node. Self-hosted Next.js allocates in the Node event loop for every request, including cache hits.

Memory stability - GioJS vs Next.js 15

The table below shows illustrative, projected figures - not measurements. It sketches the expected pattern; run the benchmark scripts in benchmarks/memory-stability/ on your own hardware for real numbers (the harness uses 50 concurrent connections, 60 seconds, 3 runs per server, RSS sampled every 5 seconds, medians across runs).

Time (s)GioJS RSS (MB)Next.js 15 RSS (MB)
085.0120.0
1085.5148.0
2086.0176.0
3086.5204.0
4087.0232.0
5087.5260.0
6088.0288.0

See benchmarks/memory-stability.md for the full methodology and for the table the harness populates with measured results.

Why GioJS stays flat

In self-hosted Next.js, the Node.js HTTP layer allocates a new buffer for every incoming request - even when the response is a cache hit. Under 50 req/s, GC pressure grows continuously and RSS climbs 2–5 MB per minute.

GioJS routes HTTP in Rust. A cache hit in the Rust layer is zero bytes allocated in Node - the response is served directly from the LRU without touching the V8 heap. Only cache misses cross the IPC boundary to Node for rendering.

Throughput

Cache-hit throughput (static pages) is bounded by Rust I/O, not Node. The figures sometimes quoted for this class of architecture (tens of thousands of cached requests/second at sub-millisecond p99) are projections, not GioJS measurements - benchmark on your own hardware before relying on specific numbers.

For dynamic pages (cache misses), throughput is similar - both are bounded by React render time.

Load testing with gio bench

gio bench ships with @gio.js/server: a zero-dependency HTTP load generator (plain node:http, keep-alive connections). It opens N concurrent connection loops for a fixed duration and reports requests/s, latency p50/p90/p99/max (nearest-rank, no sampling), non-200 count, errors, and bytes/s. Warmup requests are sent but excluded from all statistics.

bash
gio bench <url> [--connections 32] [--duration 10] [--warmup 2]
gio bench --suite /,/posts/1 --base http://localhost:3000
  • --connections - concurrent keep-alive connections (default 32)
  • --duration - measured seconds per target (default 10)
  • --warmup - unmeasured warmup seconds (default 2)
  • --suite - comma-separated paths, run sequentially and printed as an aligned table
  • --base - base URL that bare paths resolve against (default http://localhost:3000)

Each result includes the X-Gio-Cache value of the last response, so a cache-hit benchmark labels itself and cannot be silently confused with a cache-miss one. See benchmarks/README.md in the repository for the full methodology - including how to run an honest GioJS vs Next.js comparison and why localhost microbenchmarks must not be read as user-facing speedups.

Running benchmarks yourself

The benchmark infrastructure lives in benchmarks/memory-stability/:

  • run-benchmark.ps1 - Windows PowerShell script
  • run-benchmark.sh - Linux/macOS bash script
  • collect.js - parses raw samples, computes medians, writes the markdown table
  • next-baseline/ - the Next.js 15 app used as a baseline