giong

benchmarks

Benchmarks

One core per server, loopback, medians of five interleaved rounds on a 4-vCPU virtual machine; the thin line under a bar runs from the lowest round to the highest. giong is not faster than everything at everything: the charts say where the design pays off and where it does not.

Full tables and raw output: README · bench/results/

System calls per request

/json, 50 connections · lower is better

  1. giong0.03
  2. giong-http0.03
  3. Monoio (tuned)0.04
  4. Hand-written epoll2.03
  5. Raw Tokio2.05
  6. Glommio2.10

The structural difference, and the most robust result here. On the small response it buys little, because a request costs about 4 µs of kernel time whichever way it is delivered. On 8 KB responses, where a whole batch leaves in one call, it is worth almost half the CPU: 16.0 µs per request against 27.9 for raw Tokio.

bench/syscalls.sh

5,000 connections: CPU per request

/json, µs of user plus kernel time · lower is better

  1. giong4.42 µs
  2. Hand-written epoll4.99 µs
  3. giong + spin5.12 µs
  4. Monoio (tuned)5.25 µs
  5. giong-http5.37 µs
  6. Raw Tokio5.74 µs
  7. Glommio8.29 µs

giong is 11% below hand-written epoll and 16% below tuned Monoio, with the lowest p99 (26 ms; epoll 32, Monoio 44, raw Tokio 54). The likely reason is the provided buffer ring: receive buffers are reused while still warm in cache. At 50 and 1,000 connections giong, tuned Monoio and epoll are level.

bench/scale.sh

HTTPS on one core

requests per second, TLS 1.3, the same rustls on both sides · higher is better

  1. /json, kept-alive
  2. giong-http + giong-tls144.8k
  3. Axum + rustls81.0k
  4. /users, 8 KB, kept-alive
  5. giong-http + giong-tls47.8k
  6. Axum + rustls19.2k
  7. a new handshake per request
  8. giong-http + giong-tls7.1k
  9. Axum + rustls7.3k

TLS costs giong 4% on /json and 14% on 8 KB responses. Handshakes are level: a handshake is almost all cryptography, and both sides use the same library for it. Most of the /users factor is there without TLS.

bench/tls.sh

One connection: median latency

/json, microseconds · lower is better

  1. giong + spin19 µs
  2. giong28 µs
  3. Monoio (tuned)28 µs
  4. Raw Tokio29 µs
  5. Hand-written epoll29 µs
  6. giong-http30 µs
  7. Glommio33 µs

With one connection every request finds a sleeping server. spin_before_park(200 µs) keeps the worker watching its ring: 47.9k requests/s instead of 32.4k. It costs CPU, 20.5 instead of 13.6 µs per request, and the worker keeps its core busy between requests. Without it giong is the same as the others.

bench/scale.sh

File reads on one core

4 KiB at a random offset, one task, operations per second · higher is better

  1. std (blocking)1.40M
  2. giong1.01M
  3. Tokio, one core184k
  4. Tokio, 4 cores33k

5.5 times Tokio, where every read is a hand-off to another thread and back; on giong it is one entry in a batch. A blocking std call is 28% faster for cached data. What giong buys is a worker that never waits when the data is not cached, which this benchmark does not measure.

bench/fs.sh

Small responses at 50 connections: a tie

/json, requests per second

  1. giong-http195.7k
  2. Monoio (tuned)176.0k
  3. Raw Tokio174.1k
  4. giong173.5k
  5. Hand-written epoll170.9k
  6. Axum161.0k
  7. Bun147.7k
  8. Glommio143.9k
  9. Node.js39.5k

giong, tuned Monoio, raw Tokio and epoll are within noise: the kernel's TCP stack, not the runtime, is most of the cost, and wrk is the limit. giong-http reads higher and we do not know why. It costs 7% more CPU per request, as checking every header should, and keeps its core busier. Treat that row as a measurement of the setup.

bench/run.sh

What these numbers do not show

  • One machine. Everything is loopback inside a 4-vCPU virtual machine, where the load generator competes for the same few cores. The 14-core run in the README (1 to 10 workers, peak 1.50M requests/s) is loopback too, on a desktop under WSL2.
  • Not measured: a physical network, scaling on bare metal, and disks. The file benchmark reads cached data only.
  • 8 KB responses: giong and Monoio serve 63–64k requests/s, everything else 35–37k. Most of that gap comes from the virtual machine. On physical hardware expect a smaller one.
  • /compute is the same for everyone, as it should be: the time goes into the function, not the runtime.
  • Two cores are measured and are not a ranking. Two wrk threads are as likely to be the limit as the server.
  • Monoio is compared tuned, with DEFER_TASKRUN passed in. Its default ring is about 20% slower on the small response; comparing against that would flatter giong.

compare

Compared with Monoio and Glommio

The other io_uring runtimes for Rust, as they stood when the benchmarks ran. Monoio and Glommio are mature projects with wider ecosystems; this table is about how each one is built, not a ranking.

giongMonoio 0.2.4Glommio 0.9.0
PlatformsLinux ≥ 6.0Linux ≥ 5.6, with an epoll/kqueue fallback (runs on macOS)Linux ≥ 5.8, needs a raised memlock limit
Receivingmultishot, kernel-provided buffersone submission per read, caller's bufferdirect recv, io_uring to wait
Acceptingmultishotone submission per acceptone submission per accept
Buffer APIownership passingownership passingborrowed (AsyncRead / AsyncWrite)
Timeout around a readloses no dataneeds the separate cancelable_* API—
Ring defaultsDEFER_TASKRUN onno flags; pass your own builder—
Busy-poll optionyesnoyes
Multi-corebuilt-in builder with graceful draining and signals#[monoio::main(threads = N)]executor pools placed by CPU topology
SchedulingFIFO; new work placed on the least busy worker on requestFIFOweighted queues with latency classes
I/OTCP, UDP, Unix sockets, filesTCP, UDP, Unix sockets, filesTCP, UDP, Unix sockets, Direct I/O files
Ecosystemits own HTTP/1.1 server and client, web framework, TLS for both sides, DNS, PostgreSQL, MySQL and Redis clientsTLS, HTTP, codecs, Tokio compatibility layerchannels, semaphores, rate control

reproduce

Run them yourself

Each script builds the servers, runs the load generator and writes its summary to bench/results/. Run them on a quiet Linux machine; they take minutes, not seconds.