392 lines
12 KiB
Rust
392 lines
12 KiB
Rust
//! Benchmark the `x11rb_protocol::Connection` type's method, at varying levels of
|
|
//! capacity.
|
|
|
|
use criterion::{black_box, criterion_group, criterion_main, BatchSize, Criterion};
|
|
use std::{
|
|
io::{Read, Write},
|
|
mem::{replace, size_of},
|
|
net::{TcpListener, TcpStream},
|
|
thread,
|
|
};
|
|
use x11rb_protocol::{
|
|
connection::{Connection, ReplyFdKind},
|
|
protocol::xproto::{Depth, Rectangle, Screen},
|
|
x11_utils::{Serialize, TryParse},
|
|
DiscardMode, SequenceNumber,
|
|
};
|
|
|
|
#[cfg(unix)]
|
|
use std::os::unix::net::UnixStream;
|
|
|
|
fn pow(i: i32, p: i32) -> i32 {
|
|
let mut result = 1;
|
|
for _ in 0..p {
|
|
result *= i;
|
|
}
|
|
result
|
|
}
|
|
|
|
fn enqueue_packet_test(c: &mut Criterion) {
|
|
// take the cartesian product of the following conditions:
|
|
// - the packet is an event, a reply, or an error
|
|
// - pending_events and pending_replies are empty, have one element, or have
|
|
// many elements
|
|
|
|
enum PacketType {
|
|
Event,
|
|
Reply,
|
|
Error,
|
|
}
|
|
|
|
enum PacketCount {
|
|
Empty,
|
|
One,
|
|
Many,
|
|
}
|
|
|
|
use PacketCount::*;
|
|
use PacketType::*;
|
|
|
|
let mut group = c.benchmark_group("enqueue_packet");
|
|
for packet_ty in &[Event, Reply, Error] {
|
|
for packet_count in &[Empty, One, Many] {
|
|
let packet_ty_desc = match packet_ty {
|
|
Event => "event",
|
|
Reply => "reply",
|
|
Error => "error",
|
|
};
|
|
|
|
let packet_count_desc = match packet_count {
|
|
Empty => "no",
|
|
One => "one",
|
|
Many => "many",
|
|
};
|
|
|
|
let name = format!("enqueue_packet {packet_ty_desc} with {packet_count_desc} packets");
|
|
group.bench_function(name, |b| {
|
|
// generate a valid packet with the given first byte and sequence number
|
|
let mut seqno = 0u16;
|
|
let mut packet = move |ind: u8| {
|
|
let our_seqno = seqno + 1;
|
|
seqno += 1;
|
|
|
|
let mut v = vec![0; 32];
|
|
v[0] = ind;
|
|
|
|
// copy our_seqno to bytes 3 and 4
|
|
v[2..4].copy_from_slice(&our_seqno.to_ne_bytes());
|
|
|
|
v
|
|
};
|
|
|
|
// we need another one for make_conn
|
|
let mut packet2 = packet;
|
|
|
|
let queue_count = match packet_count {
|
|
PacketCount::Empty => 0,
|
|
PacketCount::One => 1,
|
|
PacketCount::Many => pow(2, 8),
|
|
};
|
|
|
|
// create a connection with the given stats
|
|
let mut make_conn = || {
|
|
let mut conn = Connection::new();
|
|
|
|
for _ in 0..queue_count {
|
|
// push a new event
|
|
conn.enqueue_packet(packet2(2));
|
|
}
|
|
|
|
for _ in 0..queue_count {
|
|
// push a new reply
|
|
conn.enqueue_packet(packet2(1));
|
|
}
|
|
|
|
conn
|
|
};
|
|
|
|
let mut conn = make_conn();
|
|
let packet = packet(match packet_ty {
|
|
Event => 2,
|
|
Reply => 1,
|
|
Error => 0,
|
|
});
|
|
|
|
b.iter(move || {
|
|
conn.enqueue_packet(packet.clone());
|
|
})
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
fn send_and_receive_request(c: &mut Criterion) {
|
|
// permutations:
|
|
// - send queue is empty or very full
|
|
// - receive queue is empty of very full
|
|
enum SendQueue {
|
|
SEmpty,
|
|
SFull,
|
|
}
|
|
|
|
enum RecvQueue {
|
|
REmpty,
|
|
RFull,
|
|
}
|
|
|
|
use RecvQueue::*;
|
|
use SendQueue::*;
|
|
|
|
let mut group = c.benchmark_group("send_and_receive_request");
|
|
|
|
for send_queue in &[SEmpty, SFull] {
|
|
for recv_queue in &[REmpty, RFull] {
|
|
let name = format!(
|
|
"send_and_receive_request (send {}, recv {})",
|
|
match send_queue {
|
|
SEmpty => "empty",
|
|
SFull => "full",
|
|
},
|
|
match recv_queue {
|
|
REmpty => "empty",
|
|
RFull => "full",
|
|
}
|
|
);
|
|
|
|
group.bench_function(name, |b| {
|
|
// create a new connection
|
|
let mut conn = Connection::new();
|
|
|
|
// if the send queue needs to be full, flood it with sent requests
|
|
if matches!(send_queue, SFull) {
|
|
for _ in 0..pow(2, 14) {
|
|
conn.send_request(match recv_queue {
|
|
REmpty => ReplyFdKind::NoReply,
|
|
RFull => ReplyFdKind::ReplyWithoutFDs,
|
|
});
|
|
}
|
|
}
|
|
|
|
// if the recv queue needs to be full, flood it with replies
|
|
if matches!(recv_queue, RFull) {
|
|
for _ in 0..pow(2, 14) {
|
|
let mut packet = vec![0; 32];
|
|
packet[0] = 1;
|
|
conn.enqueue_packet(packet);
|
|
}
|
|
}
|
|
|
|
// create a new packet
|
|
let mut packet = vec![0u8; 32];
|
|
packet[0] = 1;
|
|
|
|
b.iter(move || {
|
|
// send our request
|
|
let seq = conn.send_request(ReplyFdKind::ReplyWithoutFDs).unwrap();
|
|
|
|
// truncate to a u16
|
|
let seq_trunc = seq as u16;
|
|
|
|
// insert the sequence number at positions 2 and 3
|
|
packet[2..4].copy_from_slice(&seq_trunc.to_ne_bytes());
|
|
|
|
// enqueue the packet
|
|
conn.enqueue_packet(black_box(replace(&mut packet, vec![0u8; 32])));
|
|
|
|
// pop the reply
|
|
conn.poll_for_reply_or_error(seq)
|
|
})
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
fn try_parse_small_struct(c: &mut Criterion) {
|
|
// xproto::Rectangle is a pointer wide on 64-bit, use that
|
|
c.bench_function("try_parse an xproto::Rectangle", |b| {
|
|
let packet = [0x42u8; size_of::<Rectangle>()];
|
|
b.iter(|| Rectangle::try_parse(black_box(&packet)))
|
|
});
|
|
}
|
|
|
|
fn try_parse_large_struct(c: &mut Criterion) {
|
|
// xproto::Screen is a significantly larger structure, use that
|
|
const SCREEN_BASE_SIZE: usize = size_of::<Screen>() - size_of::<Vec<Depth>>() + size_of::<u8>();
|
|
const NUM_DEPTHS: usize = 3;
|
|
const DEPTH_SIZE: usize = 8;
|
|
const TOTAL_SIZE: usize = SCREEN_BASE_SIZE + (NUM_DEPTHS * DEPTH_SIZE);
|
|
|
|
c.bench_function("try_parse an xproto::Screen", |b| {
|
|
let mut packet = [0; TOTAL_SIZE];
|
|
packet[SCREEN_BASE_SIZE - 1] = NUM_DEPTHS as u8;
|
|
b.iter(|| Screen::try_parse(black_box(&packet)))
|
|
});
|
|
}
|
|
|
|
fn serialize_struct(c: &mut Criterion) {
|
|
// try the following:
|
|
// - send it down a TCP socket
|
|
// - send it down a Unix socket (if linux)
|
|
//
|
|
// this should relatively accurately tell us what kind of impact the buffering
|
|
// and writing have on the serialization time
|
|
//
|
|
// note that send() and recv() degenerate into sendmsg() and recvmsg(), at least
|
|
// on the Linux kernel end, so not using those functions should have no effect
|
|
enum SocketTy {
|
|
TryTcp,
|
|
TryUnix,
|
|
}
|
|
|
|
enum StructType {
|
|
Small,
|
|
Large,
|
|
}
|
|
|
|
use SocketTy::*;
|
|
use StructType::*;
|
|
|
|
let mut group = c.benchmark_group("serialize_struct");
|
|
for socket_ty in &[TryTcp, TryUnix] {
|
|
let mut fd: Box<dyn Write> = match socket_ty {
|
|
TryTcp => {
|
|
const PORT: u16 = 41234;
|
|
|
|
let listen = TcpListener::bind(("::1", PORT)).unwrap();
|
|
thread::spawn(move || {
|
|
let (mut sock, _) = listen.accept().unwrap();
|
|
|
|
// read until other sock gets dropped
|
|
let mut buf = [0u8; 1024];
|
|
loop {
|
|
if sock.read(&mut buf).is_err() {
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
let sock = TcpStream::connect(("::1", PORT)).unwrap();
|
|
Box::new(sock)
|
|
}
|
|
TryUnix => {
|
|
#[cfg(unix)]
|
|
{
|
|
let (mut left, right) = UnixStream::pair().unwrap();
|
|
thread::spawn(move || {
|
|
let mut buf = [0u8; 1024];
|
|
loop {
|
|
if left.read(&mut buf).is_err() {
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
Box::new(right)
|
|
}
|
|
#[cfg(not(unix))]
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
};
|
|
|
|
let try_desc = match socket_ty {
|
|
TryTcp => "TCP",
|
|
TryUnix => "Unix",
|
|
};
|
|
|
|
for struct_size in &[Small, Large] {
|
|
let size_desc = match struct_size {
|
|
Small => "small",
|
|
Large => "large",
|
|
};
|
|
|
|
let name = format!("serialize_struct {try_desc} {size_desc}");
|
|
group.bench_function(name, |b| {
|
|
b.iter(|| {
|
|
let bytes = match struct_size {
|
|
Small => {
|
|
let rect = Rectangle::default();
|
|
black_box(rect.serialize()).to_vec()
|
|
}
|
|
Large => {
|
|
let mut screen = Screen::default();
|
|
screen.allowed_depths.resize_with(3, Default::default);
|
|
black_box(screen.serialize())
|
|
}
|
|
};
|
|
|
|
// write the serialized bytes tothe output
|
|
fd.write_all(&bytes).unwrap();
|
|
})
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
fn discard_reply(c: &mut Criterion) {
|
|
// Measure the performance of discard_reply()
|
|
|
|
fn get_connection_and_seqnos() -> (Connection, Vec<SequenceNumber>) {
|
|
let mut conn = Connection::new();
|
|
|
|
let seqnos = (0..pow(2, 13))
|
|
.map(|_| conn.send_request(ReplyFdKind::NoReply).unwrap())
|
|
.collect();
|
|
|
|
(conn, seqnos)
|
|
}
|
|
|
|
let mut group = c.benchmark_group("discard_reply");
|
|
|
|
group.bench_function("discard oldest", |b| {
|
|
b.iter_batched(
|
|
get_connection_and_seqnos,
|
|
|(mut conn, seqnos)| {
|
|
conn.discard_reply(*seqnos.first().unwrap(), DiscardMode::DiscardReply)
|
|
},
|
|
BatchSize::SmallInput,
|
|
);
|
|
});
|
|
group.bench_function("discard newest", |b| {
|
|
b.iter_batched(
|
|
get_connection_and_seqnos,
|
|
|(mut conn, seqnos)| {
|
|
conn.discard_reply(*seqnos.last().unwrap(), DiscardMode::DiscardReply)
|
|
},
|
|
BatchSize::SmallInput,
|
|
);
|
|
});
|
|
group.bench_function("discard all forward", |b| {
|
|
b.iter_batched(
|
|
get_connection_and_seqnos,
|
|
|(mut conn, seqnos)| {
|
|
for seqno in seqnos {
|
|
conn.discard_reply(seqno, DiscardMode::DiscardReply)
|
|
}
|
|
},
|
|
BatchSize::SmallInput,
|
|
);
|
|
});
|
|
group.bench_function("discard all backward", |b| {
|
|
b.iter_batched(
|
|
get_connection_and_seqnos,
|
|
|(mut conn, seqnos)| {
|
|
for seqno in seqnos.into_iter().rev() {
|
|
conn.discard_reply(seqno, DiscardMode::DiscardReply)
|
|
}
|
|
},
|
|
BatchSize::SmallInput,
|
|
);
|
|
});
|
|
}
|
|
|
|
criterion_group!(
|
|
benches,
|
|
enqueue_packet_test,
|
|
send_and_receive_request,
|
|
try_parse_small_struct,
|
|
try_parse_large_struct,
|
|
serialize_struct,
|
|
discard_reply,
|
|
);
|
|
criterion_main!(benches);
|