throughput: send-side UDP GSO on the poll path (lever 4a) #55
No reviewers
Labels
No labels
bug
documentation
duplicate
enhancement
good first issue
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
femboy/yip!55
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/throughput-gso-4a"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
Wires send-side UDP GSO (
UDP_SEGMENT) into the default poll data-plane loop.run_poll'sflush_txnow coalesces same-destination, same-length, distinct-FEC-object datagrams into onesendmsg, cutting the per-datagram kernel send-stack cost that real-hardware profiling pinned as the single-core bottleneck. Lever 4a of a measure-gated campaign (4a GSO → 4b recv GRO → 4c MTU).Why
After the merged FEC (#51), AEAD (#52), and batched-I/O (#54) levers, kernel-stack sampling of
yipdunder load on a real target VPS showed__sys_sendmmsgas the single largest CPU slice (~40% of samples) — the dominant remaining single-core cost is the UDP send path, not crypto/FEC. Batched I/O cut syscall count; each datagram still traverses the full kernel UDP stack. GSO amortizes that stack traversal across a batch.How
crates/yip-io/src/gso.rs(new): the one FEC-safety rule, shared by both drivers —can_coalesce/partition_fate_safe/max_gso_run_len. A GSO run is same-dst, same-length, pairwise-distinctfate(FEC object id). TheUringDriverGSO path is refactored to delegate to it (behavior-preserving), so the correctness-critical rule lives in exactly one place.crates/yip-io/src/poll.rs:send_gso_payload(onesendmsg+UDP_SEGMENTcmsg; the only newunsafe),send_gso/send_gso_indexedwrappers, and a rewrittenflush_txthat partitions its egress batch into fate-safe runs, GSO-sends each run of ≥2, plain-sends singletons, and — after latching a per-run_poll"GSO unavailable" flag onEIO/EINVAL— falls back tosend_mmsg.GsoScratchis threaded throughdrain_udp/drain_tun.FEC loss-independence preserved: the distinct-
faterule guarantees a GSO skb never carries two symbols of one FEC object, so even a whole-skb drop costs each object at most one symbol — exactly recoverable. Wire-identical, latency-neutral, opportunistic (coalesces only what a burst already queued; a lone datagram sends plain).unsafestays confined toyip-io;yipdremains#![forbid(unsafe_code)].Results
Decision-gate spike (throwaway, on a real EPYC virtio box):
UDP_SEGMENTvs plainsendmmsg= 2.6× datagrams per CPU-second (462k vs 181k) — well above the 1.3× gate.Real-hardware A/B (two 1-core AMD EPYC / 967 MB virtio VPSes, same session, baseline = main
e030a39#54 no-GSO vs this branch):The end-to-end gain is smaller than the spike's 2.6× because send-side GSO only amortizes the transmit stack — recv/TUN/conntrack/IRQ don't benefit. Full tables in
crates/yip-bench/RESULTS.md.Correctness:
cargo test --workspace0 failures;yip-io46/46; clippy--all-targets -D warningsclean. netnsping_across_yipd_tunnel,..._under_loss(10% netem — FEC still recovers with GSO), andarq_recovers_bulk_lossall PASS under both the poll and uring drivers.Review
Executed via subagent-driven-development: 6 tasks, each with a dedicated spec+quality review; two review-caught findings fixed (a release-compiled-out control-buffer guard → real
Err; the GSO latch now honored within a burst, not just across calls). Final whole-branch verdict: READY WITH FOLLOW-UPS, no Critical/Important.Non-blocking follow-ups
flush_txnow runstx.clear()on theErrpath too (harmless — every caller tears down the loop onErr).f as u8cast in new test-only code.uring.rs:1852comment still names two constants that moved togso.rs.Design docs
docs/superpowers/specs/2026-07-11-throughput-gso-send-design.mddocs/superpowers/plans/2026-07-11-throughput-gso-send.md🤖 Generated with Claude Code
https://claude.ai/code/session_01RVP6NnbDMAg1iTsMMTfL86