perf: reduce packet buffer slicing churn (#2381)

* bench: add packet bytes extraction Criterion benchmark

Adds a Criterion benchmark under easytier/benches/ covering
ZCPacket::payload_bytes and tunnel_payload_bytes at 1280/4096-byte payload
sizes, using iter_batched so ZCPacket construction stays in the setup phase
and is excluded from the timed region.

- Register the [[bench]] entry in easytier/Cargo.toml.
- Document the bench and PACKET_BYTES_* env vars in benches/README.md.

* perf: reduce packet buffer slicing churn

Replace BytesMut::split_off with Buf::advance in ZCPacket bytes
extraction paths (payload_bytes, tunnel_payload_bytes, convert_type,
drop_foreign_header) and in TunZCPacketToBytes, and simplify the
copy_from_slice in new_from_payload.

When the buffer is in its unique (VEC) representation, split_off promotes
it to the shared (ARC) representation, allocating a Shared control block
and bumping the refcount on every call, and pins the buffer in shared
mode. advance only mutates the in-place ptr/len/cap fields, avoiding that
allocation/refcount churn on the TX hot path. The byte data itself is not
copied by either path.
This commit is contained in:
fanyang
2026-07-01 23:19:34 +08:00
committed by GitHub
parent 7756a15cbe
commit f24735a86f
5 changed files with 130 additions and 11 deletions
+4
View File
@@ -32,6 +32,10 @@ path = "src/lib.rs"
name = "tx_throughput" name = "tx_throughput"
harness = false harness = false
[[bench]]
name = "packet_bytes_extraction"
harness = false
[dependencies] [dependencies]
git-version = "0.3.9" git-version = "0.3.9"
+44 -1
View File
@@ -1,4 +1,47 @@
# TX Throughput Benchmark # Benchmarks
Criterion benchmarks for EasyTier hot paths.
| Bench | What it measures |
| --------------------------- | -------------------------------------------------------------------------------- |
| `tx_throughput` | End-to-end TX injection path through `peer_manager::send_msg_by_ip` |
| `packet_bytes_extraction` | `ZCPacket::payload_bytes` / `tunnel_payload_bytes` extraction (advance hot path) |
## Packet Bytes Extraction
Criterion benchmark for `ZCPacket` bytes extraction — the methods touched by the
`advance`-based slicing refactor. Measures `payload_bytes` and
`tunnel_payload_bytes` at two payload sizes (1280, 4096). Setup
(`ZCPacket::new_with_payload`) runs in the benchmark harness's preparation
phase and is excluded from the timed region, so the numbers reflect only the
extraction call.
### Quick start
```bash
cargo bench --bench packet_bytes_extraction
```
Smoke run:
```bash
PACKET_BYTES_MEASUREMENT_SECS=2 \
PACKET_BYTES_WARMUP_SECS=1 \
PACKET_BYTES_SAMPLE_SIZE=10 \
cargo bench --bench packet_bytes_extraction -- --quiet
```
### Environment variables
| Variable | Default | Notes |
| ------------------------------- | ------- | ---------------------------- |
| `PACKET_BYTES_MEASUREMENT_SECS` | `10` | Criterion `measurement_time` |
| `PACKET_BYTES_WARMUP_SECS` | `3` | Criterion `warm_up_time` |
| `PACKET_BYTES_SAMPLE_SIZE` | `10` | Criterion `sample_size` (min 10) |
---
## TX Throughput Benchmark
Criterion benchmark for EasyTier's TX injection path (`peer_manager::send_msg_by_ip`). Criterion benchmark for EasyTier's TX injection path (`peer_manager::send_msg_by_ip`).
@@ -0,0 +1,65 @@
use std::hint::black_box;
use std::time::Duration;
use criterion::{BatchSize, Criterion, Throughput, criterion_group, criterion_main};
use easytier::tunnel::packet_def::ZCPacket;
const PAYLOAD_SIZES: &[usize] = &[1280, 4096];
fn env_parse<T: std::str::FromStr>(key: &str, default: T) -> T {
std::env::var(key)
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(default)
}
fn bench_payload_bytes(c: &mut Criterion) {
let mut group = c.benchmark_group("payload_bytes");
for &size in PAYLOAD_SIZES {
let data = vec![0u8; size];
group.throughput(Throughput::Bytes(size as u64));
group.bench_with_input(format!("{size}"), &data, |b, data| {
b.iter_batched(
|| ZCPacket::new_with_payload(black_box(data)),
|p| black_box(p).payload_bytes(),
BatchSize::SmallInput,
)
});
}
group.finish();
}
fn bench_tunnel_payload_bytes(c: &mut Criterion) {
let mut group = c.benchmark_group("tunnel_payload_bytes");
for &size in PAYLOAD_SIZES {
let data = vec![0u8; size];
group.throughput(Throughput::Bytes(size as u64));
group.bench_with_input(format!("{size}"), &data, |b, data| {
b.iter_batched(
|| ZCPacket::new_with_payload(black_box(data)),
|p| black_box(p).tunnel_payload_bytes(),
BatchSize::SmallInput,
)
});
}
group.finish();
}
fn criterion_config() -> Criterion {
let measurement_secs = env_parse("PACKET_BYTES_MEASUREMENT_SECS", 10u64);
let warmup_secs = env_parse("PACKET_BYTES_WARMUP_SECS", 3u64);
let sample_size = env_parse("PACKET_BYTES_SAMPLE_SIZE", 10usize).max(10);
Criterion::default()
.measurement_time(Duration::from_secs(measurement_secs))
.warm_up_time(Duration::from_secs(warmup_secs))
.sample_size(sample_size)
}
criterion_group! {
name = benches;
config = criterion_config();
targets = bench_payload_bytes, bench_tunnel_payload_bytes
}
criterion_main!(benches);
+4 -3
View File
@@ -24,7 +24,7 @@ use crate::{
}; };
use byteorder::WriteBytesExt as _; use byteorder::WriteBytesExt as _;
use bytes::{BufMut, BytesMut}; use bytes::{Buf, BufMut, BytesMut};
use cidr::{Ipv4Inet, Ipv6Inet}; use cidr::{Ipv4Inet, Ipv6Inet};
use futures::{SinkExt, Stream, StreamExt, lock::BiLock, ready}; use futures::{SinkExt, Stream, StreamExt, lock::BiLock, ready};
use pin_project_lite::pin_project; use pin_project_lite::pin_project;
@@ -180,12 +180,13 @@ impl ZCPacketToBytes for TunZCPacketToBytes {
assert!(payload_offset >= 4); assert!(payload_offset >= 4);
let ret = if self.has_packet_info { let ret = if self.has_packet_info {
let mut inner = inner.split_off(payload_offset - 4); inner.advance(payload_offset - 4);
let proto = infer_proto(&inner[4..]); let proto = infer_proto(&inner[4..]);
self.fill_packet_info(&mut inner[0..4], proto)?; self.fill_packet_info(&mut inner[0..4], proto)?;
inner inner
} else { } else {
inner.split_off(payload_offset) inner.advance(payload_offset);
inner
}; };
tracing::debug!(?ret, ?payload_offset, "convert zc packet to tun packet"); tracing::debug!(?ret, ?payload_offset, "convert zc packet to tun packet");
+13 -7
View File
@@ -1,3 +1,4 @@
use bytes::Buf;
use bytes::Bytes; use bytes::Bytes;
use bytes::BytesMut; use bytes::BytesMut;
use zerocopy::AsBytes; use zerocopy::AsBytes;
@@ -486,7 +487,7 @@ impl ZCPacket {
let total_len = payload_off + payload.len(); let total_len = payload_off + payload.len();
ret.inner.reserve(total_len); ret.inner.reserve(total_len);
unsafe { ret.inner.set_len(total_len) }; unsafe { ret.inner.set_len(total_len) };
ret.mut_payload()[..payload.len()].copy_from_slice(payload); ret.mut_payload().copy_from_slice(payload);
ret ret
} }
@@ -587,7 +588,8 @@ impl ZCPacket {
} }
pub fn payload_bytes(mut self) -> BytesMut { pub fn payload_bytes(mut self) -> BytesMut {
self.inner.split_off(self.payload_offset()) self.inner.advance(self.payload_offset());
self.inner
} }
pub fn peer_manager_header(&self) -> Option<&PeerManagerHeader> { pub fn peer_manager_header(&self) -> Option<&PeerManagerHeader> {
@@ -652,11 +654,12 @@ impl ZCPacket {
} }
pub fn tunnel_payload_bytes(mut self) -> BytesMut { pub fn tunnel_payload_bytes(mut self) -> BytesMut {
self.inner.split_off( self.inner.advance(
self.packet_type self.packet_type
.get_packet_offsets() .get_packet_offsets()
.peer_manager_header_offset, .peer_manager_header_offset,
) );
self.inner
} }
pub fn convert_type(mut self, target_packet_type: ZCPacketType) -> Self { pub fn convert_type(mut self, target_packet_type: ZCPacketType) -> Self {
@@ -702,7 +705,8 @@ impl ZCPacket {
return Self::new_from_buf(buf, target_packet_type); return Self::new_from_buf(buf, target_packet_type);
} }
Self::new_from_buf(self.inner.split_off(new_offset), target_packet_type) self.inner.advance(new_offset);
Self::new_from_buf(self.inner, target_packet_type)
} }
pub fn into_bytes(self) -> Bytes { pub fn into_bytes(self) -> Bytes {
@@ -748,8 +752,10 @@ impl ZCPacket {
let foreign_hdr_len = hdr.get_header_len(); let foreign_hdr_len = hdr.get_header_len();
Self::new_from_buf( Self::new_from_buf(
self.inner {
.split_off(foreign_hdr_len + self.payload_offset()), self.inner.advance(foreign_hdr_len + self.payload_offset());
self.inner
},
ZCPacketType::DummyTunnel, ZCPacketType::DummyTunnel,
) )
} }