diff --git a/quinn-udp/Cargo.toml b/quinn-udp/Cargo.toml index 7a34943ce..c2ed79be2 100644 --- a/quinn-udp/Cargo.toml +++ b/quinn-udp/Cargo.toml @@ -26,3 +26,10 @@ tracing = { workspace = true } [target.'cfg(windows)'.dependencies] windows-sys = { version = "0.52.0", features = ["Win32_Foundation", "Win32_System_IO", "Win32_Networking_WinSock"] } once_cell = "1.19.0" + +[dev-dependencies] +criterion = "0.5" + +[target.'cfg(any(target_os = "linux", target_os = "windows"))'.bench] +name = "throughput" +harness = false diff --git a/quinn-udp/benches/throughput.rs b/quinn-udp/benches/throughput.rs new file mode 100644 index 000000000..39ea6a291 --- /dev/null +++ b/quinn-udp/benches/throughput.rs @@ -0,0 +1,75 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +use quinn_udp::{RecvMeta, Transmit, UdpSocketState}; +use std::cmp::min; +use std::{io::IoSliceMut, net::UdpSocket, slice}; + +pub fn criterion_benchmark(c: &mut Criterion) { + const TOTAL_BYTES: usize = 10 * 1024 * 1024; + // Maximum GSO buffer size is 64k. + const MAX_BUFFER_SIZE: usize = u16::MAX as usize; + const SEGMENT_SIZE: usize = 1280; + + let send = UdpSocket::bind("[::1]:0") + .or_else(|_| UdpSocket::bind("127.0.0.1:0")) + .unwrap(); + let recv = UdpSocket::bind("[::1]:0") + .or_else(|_| UdpSocket::bind("127.0.0.1:0")) + .unwrap(); + let max_segments = min( + UdpSocketState::new((&send).into()) + .unwrap() + .max_gso_segments(), + MAX_BUFFER_SIZE / SEGMENT_SIZE, + ); + let dst_addr = recv.local_addr().unwrap(); + let send_state = UdpSocketState::new((&send).into()).unwrap(); + let recv_state = UdpSocketState::new((&recv).into()).unwrap(); + // Reverse non-blocking flag set by `UdpSocketState` to make the test non-racy + recv.set_nonblocking(false).unwrap(); + + let mut receive_buffer = vec![0; MAX_BUFFER_SIZE]; + let mut meta = RecvMeta::default(); + + for gso_enabled in [false, true] { + let mut group = c.benchmark_group(format!("gso_{}", gso_enabled)); + group.throughput(criterion::Throughput::Bytes(TOTAL_BYTES as u64)); + + let segments = if gso_enabled { max_segments } else { 1 }; + let msg = vec![0xAB; SEGMENT_SIZE * segments]; + + let transmit = Transmit { + destination: dst_addr, + ecn: None, + contents: &msg, + segment_size: gso_enabled.then_some(SEGMENT_SIZE), + src_ip: None, + }; + + group.bench_function("throughput", |b| { + b.iter(|| { + let mut sent: usize = 0; + while sent < TOTAL_BYTES { + send_state.send((&send).into(), &transmit).unwrap(); + sent += transmit.contents.len(); + + let mut received_segments = 0; + while received_segments < segments { + let n = recv_state + .recv( + (&recv).into(), + &mut [IoSliceMut::new(&mut receive_buffer)], + slice::from_mut(&mut meta), + ) + .unwrap(); + assert_eq!(n, 1); + received_segments += meta.len / meta.stride; + } + assert_eq!(received_segments, segments); + } + }) + }); + } +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches);