Skip to content

perf(xudp): avoid temporary buffer allocation in WriteTo - #17

Open
Tunglies wants to merge 1 commit into
SagerNet:devfrom
Tunglies:perf/upstream-xudp-write-to-allocation
Open

Tunglies wants to merge 1 commit into
SagerNet:devfrom
Tunglies:perf/upstream-xudp-write-to-allocation

Conversation

@Tunglies

@Tunglies Tunglies commented Nov 10, 2026 •

Copy link
Copy Markdown

Summary

XUDPConn.WriteTo wraps the caller's payload with buf.As before passing it to bufio.WritePacketBuffer, which then allocates the buffer used for transmission and copies the payload into it.

Allocate that transmission buffer directly with the required front and rear headroom, copy the payload, and pass it to the existing WritePacket method. This removes the temporary wrapper while preserving the payload copy, packet encoding, and return behavior.

The change is limited to XUDPConn.WriteTo: one file, 10 insertions and 1 deletion.

Measurements

The same method change was measured in the MetaCubeX fork through a native macOS TUN with the complete mihomo process. These numbers describe that consumer and dependency graph, not a fresh performance measurement of this upstream revision:

UDP generator → TUN → mips → mihomo → VLESS/XUDP → local server → UDP echo, with replies following the reverse path.

  • Apple M5 Pro, macOS 27.2, Go 1.27.3.
  • mihomo 478af2a57c48bdb8e85f6ed93b2c3735e90ceabd, sing-vmess base 9b7fdbf462a5cf6366628631ef426c1a91f5df82.
  • Identical dependencies and configuration; only the WriteTo patch differs.
  • Four alternating A/B pairs per workload, 32 measured runs, 8 million roundtrips.
  • Separate generator, server, and mihomo processes; allocation counters below cover mihomo only.
  • GOMAXPROCS=4 per process, one UDP socket per flow, batches of 32 requests.
  • Mixed payloads: 80% 1400 B, 10% 512 B, 10% 64 B.
Workload Allocated B/roundtrip, before → after Paired change Allocations/roundtrip, before → after
Mixed, 1 flow 2561.69 → 2497.41 −2.51% 56.060 → 55.057
Mixed, 4 flows 2562.64 → 2498.66 −2.50% 56.065 → 55.063
1400 B, 1 flow 2556.03 → 2493.06 −2.50% 56.033 → 55.032
1400 B, 4 flows 2558.45 → 2493.94 −2.53% 56.037 → 55.036

Absolute values are medians across four runs per variant. Changes are medians of the matched-pair ratios.

The patch saves approximately 64 bytes and one allocation per roundtrip in this workload, reducing full-process allocated bytes by about 2.5%. Paired median throughput changes ranged from −0.63% to +0.66%; these measurements do not establish a throughput, CPU, or resident-memory improvement.

All 8 million roundtrips passed sequence and payload validation. CPU profiles were collected separately from the timed runs.

Upstream validation

Based on SagerNet/sing-vmess d800eb57f96c184426196881b76967db6f6b60e5, with its existing sing dependency unchanged. go test ./... and targeted race checks passed. Local equivalence checks compare the new method against the existing helper for first/subsequent packets, IPv4/IPv6/domain destinations, payload sizes from 0 to 16000 bytes, upstream headroom, buffer release, caller-data preservation, and write-error/short-write return behavior.

@Tunglies
Tunglies marked this pull request as ready for review November 10, 2026 11:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant