Compare KCP & QUIC transports in Rust — benchmarks & proxy harness
This project compares transport implementations in Rust: KCP (kcp-tokio, kcp-deepseek, kcprs, ys-kcp, kcp-sys) and QUIC (quinn). It includes a TCP-over-KCP proxy and echo/load-test tools; benchmarks measure throughput, latency, and concurrency across all implementations (see tables below).
cargo bench| Group | Description |
|---|---|
| kcp_tokio_* | Real UDP (kcp-tokio), 64B–8KB, latency, concurrent |
| quinn_* | QUIC over UDP with TLS (quinn) |
| kcp_deepseek_* | UDP localhost (deepseeksss/kcp + bench bridge) |
| kcprs_* | UDP localhost (kcprs), pure Rust + bench bridge |
| ys_kcp_* | UDP localhost (ys-kcp fork with send fix); optional, nightly |
| slipstream_* | QUIC over UDP (slipstream-picoquic); optional |
| kcp_sys_* | UDP localhost (kcp-sys), endpoints wired through tokio UDP; optional, libclang (throughput + latency) |
Refresh results: .\scripts\bench_to_readme.ps1 (default = all). -Lib default | ys_kcp | kcp_sys for one; -SkipRun = no run, just update README from existing target\criterion.
Generated by scripts/bench_to_readme.ps1. Run cargo bench then this script to refresh.
Last updated: 2026-03-01 09:09 Hardware: 13th Gen Intel(R) Core(TM) i7-13700K (16 cores / 24 logical), 63.75 GB RAM, Microsoft Windows 11 Pro
| Payload | kcp_tokio (UDP) | quinn (QUIC) | slipstream_picoquic | kcp_deepseek | kcprs | ys_kcp | kcp_sys |
|---|---|---|---|---|---|---|---|
| 64 B | 148.62 µs | 81.95 µs | 89.11 µs | 1.12 ms | 1.14 ms | 1.14 ms | 8.50 ms |
| 256 B | 158.25 µs | 85.10 µs | 79.16 µs | 1.09 ms | 1.12 ms | 1.12 ms | 18.93 ms |
| 1 KiB | 153.99 µs | 86.44 µs | 85.81 µs | 1.10 ms | 1.12 ms | 1.13 ms | 10.29 ms |
| 4 KiB | 262.05 µs | 120.65 µs | 203.13 µs | 3.26 ms | -¹ | 2.13 ms | 4.12 ms |
| 8 KiB | 420.61 µs | 154.83 µs | 290.72 µs | 1.74 ms | -¹ | 2.51 ms | 1.15 ms |
¹
kcprs4 KiB/8 KiB skipped (see throughput notes).kcp_deepseek/kcprs/ys_kcptimes include ~1 ms synchronous polling floor; see throughput methodology notes.
| Payload | kcp_tokio (UDP) | quinn (QUIC) | slipstream_picoquic | kcp_deepseek | kcprs | ys_kcp | kcp_sys |
|---|---|---|---|---|---|---|---|
| 64 B | 420.53 KiB/s | 762.64 KiB/s | 701.41 KiB/s | 55.74 KiB/s | 54.61 KiB/s | 55.04 KiB/s | 7.35 KiB/s² |
| 256 B | 1.54 MiB/s | 2.87 MiB/s | 3.08 MiB/s | 230.41 KiB/s | 223.89 KiB/s | 222.43 KiB/s | 13.21 KiB/s² |
| 1 KiB | 6.34 MiB/s | 11.30 MiB/s | 11.38 MiB/s | 910.55 KiB/s | 895.08 KiB/s | 888.82 KiB/s | 97.16 KiB/s² |
| 4 KiB | 14.91 MiB/s | 32.38 MiB/s | 19.23 MiB/s | 1.20 MiB/s | -¹ | 1.83 MiB/s | 969.74 KiB/s² |
| 8 KiB | 18.57 MiB/s | 50.46 MiB/s | 26.87 MiB/s | 4.49 MiB/s | -¹ | 3.11 MiB/s | 6.77 MiB/s² |
Methodology notes:
kcp_tokioandquinnreuse a single connection (and stream, for quinn) across all hot-path iterations per payload size — only the per-roundtrip cost is measured.quinnuses length-prefix framing to keep the QUIC stream open without callingfinish(), matchingkcp_tokio's stream-reuse pattern.kcp_deepseek,kcprs, andys_kcpuse a synchronous polling harness: both client and server run in the same thread and exchange UDP datagrams via a busy-wait loop (200 µs sleep per iteration). The ~1 ms floor in their results reflects this polling overhead, not raw protocol throughput.kcp_tokioandquinnuse a real async I/O server with OS-scheduled I/O.- ¹
kcprs4 KiB/8 KiB skipped:kcprs-0.5.0panics on multi-segment payloads due to an out-of-bounds bug inparse_ack()(snd_buf.remove(i)inside a non-breaking loop oversnd_buf.len()).- ²
kcp_systhroughput reinitializes client and server per iteration (the kcp-sys server exits after the first stream closes, causing subsequentaccept()calls to hang). These numbers measure connect+roundtrip cost, not sustained throughput — treat them as equivalent to the latency bench.
| Payload | kcp_tokio (UDP) | quinn (QUIC) | slipstream_picoquic | kcp_deepseek | kcprs | ys_kcp | kcp_sys |
|---|---|---|---|---|---|---|---|
| 64 B | 100% | 100% | 100% | 100% | 100% | 100% | 100% |
| 256 B | 100% | 100% | 100% | 100% | 100% | 100% | 100% |
| 1 KiB | 100% | 100% | 100% | 100% | 100% | 100% | 100% |
| 4 KiB | 100% | 100% | 100% | 100% | - | 100% | 100% |
| 8 KiB | 100% | 100% | 100% | 100% | - | 100% | 100% |
| Benchmark | kcp_tokio (UDP) | quinn (QUIC) | slipstream_picoquic | kcp_deepseek | kcprs | ys_kcp | kcp_sys |
|---|---|---|---|---|---|---|---|
| 64 B RTT | 3.13 ms | 552.31 µs | 1.19 ms | 1.11 ms | 1.16 ms | 1.18 ms | 9.14 ms |
| Success rate | 99.9% | 100% | 100% | 100% | 100% | 100% | 99.9% |
The proxy server, proxy client, and load_test binaries support a selectable transport: kcp-tokio (KCP over UDP) or quinn (QUIC over UDP). Use --transport to choose; server and client must use the same transport.
| Transport | Description |
|---|---|
| kcp-tokio | KCP over UDP (default) |
| quinn | QUIC over UDP with TLS (self-signed cert; client accepts any cert for local use) |
# Server: KCP echo on 0.0.0.0:12345 (default transport)
cargo run --bin proxy_server -- --listen 0.0.0.0:12345
# Server: QUIC echo on 0.0.0.0:12345
cargo run --bin proxy_server -- --transport quinn --listen 0.0.0.0:12345# Server: KCP on 0.0.0.0:12345 -> TCP 127.0.0.1:8080
cargo run --bin proxy_server -- --listen 0.0.0.0:12345 --upstream 127.0.0.1:8080
# Server: QUIC on 0.0.0.0:12345 -> TCP 127.0.0.1:8080
cargo run --bin proxy_server -- --transport quinn --listen 0.0.0.0:12345 --upstream 127.0.0.1:8080# Client: TCP 127.0.0.1:9000 -> KCP proxy at 127.0.0.1:12345
cargo run --bin proxy_client -- --listen 127.0.0.1:9000 --proxy 127.0.0.1:12345
# Client: TCP 127.0.0.1:9000 -> QUIC proxy at 127.0.0.1:12345
cargo run --bin proxy_client -- --transport quinn --listen 127.0.0.1:9000 --proxy 127.0.0.1:12345Then connect to 127.0.0.1:9000 with any TCP client; traffic is tunneled over the selected transport to the server.
Start the echo server first (with the same transport you will use for the load test), then:
# KCP: 10 connections, 100 messages each, 512-byte messages
cargo run --release --bin load_test -- --server 127.0.0.1:12345 --connections 10 --messages 100 --message-size 512
# QUIC load test (server must be started with --transport quinn)
cargo run --release --bin load_test -- --transport quinn --server 127.0.0.1:12345 --connections 10 --messages 100
# Run for 5 seconds
cargo run --release --bin load_test -- --server 127.0.0.1:12345 --connections 10 --duration-secs 5cargo testRuns unit tests and integration tests (echo: single/multiple/large message over KCP).
See DEVELOPMENT.md for build and development (format, clippy).
- kcp-tokio 0.4 — async KCP on Tokio (selectable:
--transport kcp-tokio) - quinn 0.11 — QUIC over UDP (selectable:
--transport quinn; server/client/load_test) - kcp (git) — core KCP protocol for benchmarks
- kcprs 0.5 — pure Rust KCP for benchmarks
- ys-kcp (fork of ys4e/ys-kcp, includes fix for segment payload in
send()), kcp-sys — optional (benchmarks only):--features ys-kcp(nightly) or--features kcp-sys(libclang) - tokio, tracing, anyhow, clap, rcgen, rustls
MIT
Made with ❤️