142 lines
5.7 KiB
Rust
142 lines
5.7 KiB
Rust
use super::{Limits, Runtime, Socket, net};
|
|
use bytes::BufMut;
|
|
use std::{io::ErrorKind, time::Instant};
|
|
use tokio::{io::AsyncWriteExt, net::TcpStream};
|
|
|
|
async fn independent_read(socket: &TcpStream, limit: usize) -> Option<Vec<u8>> {
|
|
socket.readable().await.unwrap();
|
|
let mut bytes = Vec::new();
|
|
bytes.try_reserve_exact(limit).unwrap();
|
|
loop {
|
|
let result = socket.try_read_buf(&mut (&mut bytes).limit(limit));
|
|
match result {
|
|
Ok(count) => return (count != 0).then_some(bytes),
|
|
Err(error) if error.kind() == ErrorKind::WouldBlock => {
|
|
socket.readable().await.unwrap();
|
|
}
|
|
Err(error) => panic!("independent TCP read failed: {error}"),
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn sample(
|
|
peer: &mut TcpStream,
|
|
payload: &[u8],
|
|
limit: usize,
|
|
iterations: usize,
|
|
mut read: impl AsyncFnMut() -> Option<Vec<u8>>,
|
|
) -> (u128, usize, usize) {
|
|
let mut elapsed = 0;
|
|
let mut reads = 0;
|
|
let mut total = 0;
|
|
for _ in 0..iterations {
|
|
peer.write_all(payload).await.unwrap();
|
|
let mut received = 0;
|
|
loop {
|
|
let started = Instant::now();
|
|
let bytes = read().await;
|
|
elapsed += started.elapsed().as_nanos();
|
|
reads += 1;
|
|
let Some(bytes) = bytes else {
|
|
assert!(payload.is_empty());
|
|
break;
|
|
};
|
|
assert!(!bytes.is_empty() && bytes.len() <= limit);
|
|
assert!(received + bytes.len() <= payload.len());
|
|
assert_eq!(bytes, payload[received..received + bytes.len()]);
|
|
received += bytes.len();
|
|
total += bytes.len();
|
|
if received == payload.len() {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
assert_eq!(total, payload.len() * iterations);
|
|
(elapsed, reads, total)
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "explicit release performance probe; http enables the independent bytes/io-util peer"]
|
|
fn tcp_read_buffer_cost() {
|
|
let runtime = Runtime::new(Limits {
|
|
threads: 1,
|
|
workers: 1,
|
|
tasks: 8,
|
|
})
|
|
.unwrap();
|
|
runtime
|
|
.run(async |scope| {
|
|
eprintln!(
|
|
"tcp_read_buffer: 15 fixed rounds; one async worker; two persistent loopback pairs; \
|
|
peer writes, byte verification and result drops excluded from read timing; \
|
|
rust_independent uses Tokio try_read_buf and bytes::BufMut::limit directly; \
|
|
no shared Dever read implementation; direct peer excludes alias-close and read-lock checks; \
|
|
actual read counts are retained; empty payload cases measure established EOF"
|
|
);
|
|
println!("\ncase,sample,implementation,iterations,elapsed_ns,reads,bytes");
|
|
for (limit, payload_size, iterations) in [
|
|
(64, 64, 4096),
|
|
(65_536, 64, 2048),
|
|
(1_048_576, 64, 512),
|
|
(65_536, 65_536, 256),
|
|
(1_048_576, 65_536, 128),
|
|
(65_536, 0, 512),
|
|
(1_048_576, 0, 128),
|
|
] {
|
|
let listener = net::listen("127.0.0.1", 0, scope).await.unwrap();
|
|
let mut dever_peer =
|
|
TcpStream::connect(("127.0.0.1", listener.port().unwrap() as u16))
|
|
.await
|
|
.unwrap();
|
|
let socket: Socket = listener.accept().await.unwrap();
|
|
listener.close().unwrap();
|
|
let listener = tokio::net::TcpListener::bind(("127.0.0.1", 0)).await.unwrap();
|
|
let mut independent_peer = TcpStream::connect(listener.local_addr().unwrap())
|
|
.await
|
|
.unwrap();
|
|
let independent = listener.accept().await.unwrap().0;
|
|
independent.set_nodelay(true).unwrap();
|
|
dever_peer.set_nodelay(true).unwrap();
|
|
independent_peer.set_nodelay(true).unwrap();
|
|
drop(listener);
|
|
if payload_size == 0 {
|
|
dever_peer.shutdown().await.unwrap();
|
|
independent_peer.shutdown().await.unwrap();
|
|
}
|
|
let payload = (0..payload_size)
|
|
.map(|position| (position % 251) as u8)
|
|
.collect::<Vec<_>>();
|
|
sample(&mut dever_peer, &payload, limit, 16, async || {
|
|
socket.read(limit as i64).await.unwrap()
|
|
})
|
|
.await;
|
|
sample(&mut independent_peer, &payload, limit, 16, async || {
|
|
independent_read(&independent, limit).await
|
|
})
|
|
.await;
|
|
for round in 0..15 {
|
|
let mut order = ["dever", "rust_independent", "control"];
|
|
order.rotate_left(round % 3);
|
|
for label in order {
|
|
let (elapsed, reads, total) = if label == "rust_independent" {
|
|
sample(&mut independent_peer, &payload, limit, iterations, async || {
|
|
independent_read(&independent, limit).await
|
|
})
|
|
.await
|
|
} else {
|
|
sample(&mut dever_peer, &payload, limit, iterations, async || {
|
|
socket.read(limit as i64).await.unwrap()
|
|
})
|
|
.await
|
|
};
|
|
println!(
|
|
"tcp_read_{limit}_{payload_size},{round},{label},{iterations},{elapsed},{reads},{total}"
|
|
);
|
|
}
|
|
}
|
|
socket.close().unwrap();
|
|
}
|
|
})
|
|
.unwrap();
|
|
}
|