-
Notifications
You must be signed in to change notification settings - Fork 41
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #690 from bread-graphics/benchmarking
Add connection benchmarking to x11rb-protocol
- Loading branch information
Showing
2 changed files
with
342 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,335 @@ | ||
//! Benchmark the `x11rb_protocol::Connection` type's method, at varying levels of | ||
//! capacity. | ||
use criterion::{black_box, criterion_group, criterion_main, 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}, | ||
}; | ||
|
||
#[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 {} with {} packets", | ||
packet_ty_desc, packet_count_desc | ||
); | ||
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 | ||
(&mut 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 | ||
(&mut 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(); | ||
}) | ||
}); | ||
} | ||
} | ||
} | ||
|
||
criterion_group!( | ||
benches, | ||
enqueue_packet_test, | ||
send_and_receive_request, | ||
try_parse_small_struct, | ||
try_parse_large_struct, | ||
serialize_struct | ||
); | ||
criterion_main!(benches); |