Performance History
Pre-v1.6.1 baselines — the v1.3.0 splice-introduction tables and the v0.11 pre-splice iptables comparison, preserved verbatim for traceability.
This page preserves the baselines that precede the current v1.6.1 report. It is kept verbatim so each successive report remains reproducible against a fixed prior reference — there is no operational reason to consult it during day-to-day use of Portunus.
- v1.3.0 introduced the Linux TCP
splicefast path and is the first release to close the gap toiptablesabove 12.5 Gbit/s. Measured on the original Debian 13 host. - v0.11 is the last pre-splice baseline. The v1.3.0 numbers were measured against it.
The current head-to-head (re-measured on the same host, plus a
portunus-standalone vs portunus-client comparison) is the
v1.6.1 report.
v1.3.0 baseline (splice fast path introduction)
Captured on the original dedicated Linux bench host (Debian 13, kernel 6.12.74-2, glibc 2.41 — same hardware as the v0.11 iptables baseline below).
v1.3.0 Headline (Linux TCP splice fast path)
The v1.3.0 splice fast path more than doubles single-flow uncapped throughput on plain TCP rules with no per-rule and no per-owner bandwidth cap:
| Mode | Direct iperf3 loopback | Portunus uncapped | Portunus vs direct | Speedup vs v1.2.0 userspace |
|---|---|---|---|---|
splice OFF (PORTUNUS_DISABLE_SPLICE=1, v1.2.0 userspace) | 18,288 Mbit/s | 9,954 Mbit/s | 54.4% | (baseline) |
| splice ON (v1.3.0 default) | 18,820 Mbit/s | 21,922 Mbit/s | 116.5% | 2.20× |
The splice ON case exceeds the direct iperf3 loopback baseline because the
kernel-to-kernel splice + pipe path avoids the userspace read → memcpy → write round-trip that bounds every userspace TCP application on this host
— including iperf3 itself.
Offered-load sweep (v1.3.0 vs v0.11 saturation point)
The v0.11 report identified ≥ 12.5 Gbit/s as the offered-load band where
Portunus saturates early and falls well behind iptables. The v1.3.0 splice
fast path closes that gap on this host. Same 11-point sweep, same iptables
REDIRECT comparison, same host — captured under v1.3.0 with splice ON
(default) and splice OFF (PORTUNUS_DISABLE_SPLICE=1) back-to-back:
| Offered load | Direct | iptables REDIRECT | Portunus + splice OFF | Portunus + splice ON | splice speedup | splice OFF vs iptables | splice ON vs iptables |
|---|---|---|---|---|---|---|---|
| 100 Mbit/s | 100 | 100 | 100 | 100 | 1.00× | 100.0% | 100.0% |
| 500 Mbit/s | 500 | 500 | 500 | 500 | 1.00× | 100.0% | 100.0% |
| 1 Gbit/s | 1,000 | 1,000 | 1,000 | 1,000 | 1.00× | 100.0% | 100.0% |
| 2.5 Gbit/s | 2,500 | 2,500 | 2,500 | 2,498 | 1.00× | 100.0% | 99.9% |
| 5 Gbit/s | 4,999 | 5,005 | 5,207 | 4,999 | 0.96× | 104.0% | 99.9% |
| 7.5 Gbit/s | 7,476 | 7,492 | 6,775 | 7,499 | 1.11× | 90.4% | 100.1% |
| 10 Gbit/s | 9,999 | 10,484 | 8,870 | 10,035 | 1.13× | 84.6% | 95.7% |
| 12.5 Gbit/s | 12,598 | 12,545 | 8,556 | 12,373 | 1.45× | 68.2% | 98.6% |
| 15 Gbit/s | 15,569 | 15,523 | 9,953 | 16,370 | 1.64× | 64.1% | 105.5% |
| 18 Gbit/s | 18,406 | 18,017 | 8,347 | 19,618 | 2.35× | 46.3% | 108.9% |
| 20 Gbit/s | 19,088 | 18,738 | 8,690 | 20,147 | 2.32× | 46.4% | 107.5% |
v1.3.0 splice ON reaches 12,373 Mbit/s (98.6% of iptables) at 12.5 Gbit/s offered, versus the v0.11 6,529 Mbit/s (49% of iptables). Through the 15-20 Gbit/s band the splice ON path stays within iperf3 short-run noise of both the direct loopback and the iptables REDIRECT baseline, while the splice OFF userspace path saturates around the v1.2.0 userspace-memcpy ceiling of ~8.5-10 Gbit/s.
v0.11 baseline (pre-splice)
Linux iptables Baseline
Captured on 2026-05-10 on:
Linux host 6.12.74+deb13+1-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.12.74-2 (2026-03-08) x86_64
rustc 1.95.0 (59807616e 2026-04-14)
iperf 3.18Command:
TMPDIR=/root scripts/perf_loopback.py --seconds 10 --omit-seconds 2 \
--with-iptables --iptables-bin /usr/sbin/iptablesTMPDIR=/root was required on this host because /tmp is tmpfs, and
portunus-server intentionally refuses tmpfs-backed TLS / persistence state.
The iptables rule was cleaned up after the run; iptables -t nat -S OUTPUT
returned only the default -P OUTPUT ACCEPT policy.
| Path | Throughput | vs direct | vs iptables | Retransmits |
|---|---|---|---|---|
Direct iperf3 loopback | 18,362-18,770 Mbit/s | 100.00% | 98-100% | 0 |
iptables nat/OUTPUT REDIRECT | 18,429-19,190 Mbit/s | 100-102% | 100.00% | 0 |
iperf3 through Portunus uncapped rule | 7,273-9,844 Mbit/s | 39-53% | 39-53% | 0-21 |
Portunus capped at 1,048,576 B/s | 8.387 Mbit/s | n/a | n/a | 0 |
The uncapped maximum is intentionally reported as a range: at this speed the measurement is dominated by scheduler noise, TCP pacing, and userspace-copy CPU placement.
Offered-Load Sweep
Command shape:
TMPDIR=/root scripts/perf_loopback.py --seconds 5 --omit-seconds 1 \
--with-iptables --offered-mbps 100,500,1000,2500,5000,7500,10000,12500,15000,18000,20000 \
--iptables-bin /usr/sbin/iptables| Offered load | Direct | iptables REDIRECT | Portunus | Portunus vs iptables | Interpretation |
|---|---|---|---|---|---|
| 100 Mbit/s | 100.012 | 100.007 | 99.999 | 99.99% | No meaningful difference. |
| 500 Mbit/s | 499.986 | 500.011 | 500.138 | 100.03% | No meaningful difference. |
| 1 Gbit/s | 999.850 | 999.737 | 999.694 | 100.00% | No meaningful difference. |
| 2.5 Gbit/s | 2,499.613 | 2,499.853 | 2,499.678 | 99.99% | No meaningful difference. |
| 5 Gbit/s | 4,999.610 | 4,999.626 | 5,351.013 | 107.03% | At target; short-run pacing noise dominates. |
| 7.5 Gbit/s | 7,471.440 | 7,499.448 | 8,172.746 | 108.98% | At target; short-run pacing noise dominates. |
| 10 Gbit/s | 10,035.228 | 9,999.445 | 9,422.687 | 94.23% | Small visible gap, still close to target. |
| 12.5 Gbit/s | 12,753.687 | 13,198.878 | 6,528.820 | 49.46% | Gap opens; Portunus is CPU/userspace-copy limited. |
| 15 Gbit/s | 15,482.215 | 16,335.550 | 7,836.651 | 47.97% | Clear gap. |
| 18 Gbit/s | 18,228.201 | 17,948.877 | 7,919.914 | 44.12% | Clear gap. |
| 20 Gbit/s | 18,600.141 | 18,265.199 | 6,105.970 | 33.43% | Beyond this host's Portunus comfortable range. |
Above 12.5 Gbit/s offered, iptables stays near the offered load while the pre-splice Portunus userspace path saturates much earlier. This is the band the v1.3.0 splice fast path closed (see above), and that the v1.6.1 report confirms on the same host.