1use std::hint::black_box;
11use std::time::Instant;
12
13use simoom_h2::{Config, Connection, Event};
14use simoom_hpack::{Decoder, Encoder};
15
16const BODY: &[u8] = b"Hello, world!";
17
18fn transfer(from: &mut Connection, buf: &mut Vec<u8>) {
19 buf.clear();
20 buf.extend_from_slice(from.output());
21 from.advance_output(buf.len());
22}
23
24fn pair() -> (Connection, Connection, Vec<u8>) {
25 let cfg = Config { window_autotune: false, ..Config::default() };
26 let mut c = Connection::client(cfg.clone());
27 let mut s = Connection::server(cfg);
28 let mut buf = Vec::with_capacity(64 * 1024);
29 for _ in 0..3 {
30 transfer(&mut c, &mut buf);
31 s.recv(&buf, 0, &mut |_| {});
32 transfer(&mut s, &mut buf);
33 c.recv(&buf, 0, &mut |_| {});
34 }
35 (c, s, buf)
36}
37
38fn batch(c: &mut Connection, s: &mut Connection, buf: &mut Vec<u8>, ids: &mut Vec<u32>, streams: usize) -> usize {
40 for _ in 0..streams {
41 c.send_request(b"GET", b"https", b"example.com", b"/hello", &[(b"user-agent", b"simoom-bench"), (b"accept", b"*/*")], true).unwrap();
42 }
43 transfer(c, buf);
44 ids.clear();
45 let n = s.recv(buf, 0, &mut |e| {
46 if let Event::Headers { stream, .. } = e {
47 ids.push(stream);
48 }
49 });
50 debug_assert_eq!(n, buf.len());
51 for &id in ids.iter() {
52 s.send_response(id, 200, &[(b"content-type", b"text/plain")], false).unwrap();
53 s.send_data(id, BODY, true).unwrap();
54 s.stream_finished(id);
55 }
56 transfer(s, buf);
57 let mut got = 0;
58 c.recv(buf, 0, &mut |e| {
59 if let Event::Data { data, .. } = e {
60 got += data.len();
61 }
62 });
63 got
64}
65
66fn bench_requests(streams: usize, total: usize) -> f64 {
67 let (mut c, mut s, mut buf) = pair();
68 let mut ids = Vec::with_capacity(streams);
69 for _ in 0..100 {
71 batch(&mut c, &mut s, &mut buf, &mut ids, streams);
72 }
73 let rounds = total / streams;
74 let t = Instant::now();
75 let mut got = 0;
76 for _ in 0..rounds {
77 got += batch(&mut c, &mut s, &mut buf, &mut ids, streams);
78 }
79 let ns = t.elapsed().as_nanos() as f64 / (rounds * streams) as f64;
80 assert_eq!(got, rounds * streams * BODY.len());
81 assert_eq!(s.tracked_streams(), 0);
82 ns
83}
84
85const REQ: [(&[u8], &[u8]); 6] = [
86 (b":method", b"GET"),
87 (b":scheme", b"https"),
88 (b":authority", b"example.com"),
89 (b":path", b"/hello"),
90 (b"user-agent", b"simoom-bench"),
91 (b"accept", b"*/*"),
92];
93
94fn bench_hpack(iters: usize) -> (f64, f64, f64, f64, usize) {
95 let mut enc = Encoder::new(4096);
96 let mut dec = Decoder::new(4096, 16 * 1024);
97 let mut block = Vec::with_capacity(256);
98 enc.encode(&REQ, &mut block);
99 dec.decode(&block, &mut |_, _| {}).unwrap();
100 let t = Instant::now();
101 for _ in 0..iters {
102 block.clear();
103 enc.encode(black_box(&REQ), &mut block);
104 }
105 let enc_warm = t.elapsed().as_nanos() as f64 / iters as f64;
106 let warm_len = block.len();
107 let t = Instant::now();
108 let mut n = 0;
109 for _ in 0..iters {
110 dec.decode(black_box(&block), &mut |a, b| n += a.len() + b.len()).unwrap();
111 }
112 let dec_warm = t.elapsed().as_nanos() as f64 / iters as f64;
113 black_box(n);
114 let t = Instant::now();
116 for _ in 0..iters {
117 let mut e = Encoder::new(4096);
118 block.clear();
119 e.encode(black_box(&REQ), &mut block);
120 black_box(&block);
121 }
122 let enc_cold = t.elapsed().as_nanos() as f64 / iters as f64;
123 let t = Instant::now();
124 for _ in 0..iters {
125 let mut d = Decoder::new(4096, 16 * 1024);
126 d.decode(black_box(&block), &mut |a, b| n += a.len() + b.len()).unwrap();
127 }
128 let dec_cold = t.elapsed().as_nanos() as f64 / iters as f64;
129 black_box(n);
130 (enc_warm, dec_warm, enc_cold, dec_cold, warm_len)
131}
132
133fn bench_huffman(iters: usize) -> f64 {
134 let s = b"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko)";
135 let mut h = Vec::new();
136 simoom_hpack::huffman::encode(s, &mut h);
137 let mut out = Vec::with_capacity(128);
138 let t = Instant::now();
139 for _ in 0..iters {
140 out.clear();
141 simoom_hpack::huffman::decode(black_box(&h), &mut out).unwrap();
142 }
143 let ns = t.elapsed().as_nanos() as f64 / iters as f64;
144 assert_eq!(out, s);
145 ns / s.len() as f64
146}
147
148fn main() {
149 let total: usize = std::env::args().nth(1).and_then(|a| a.parse().ok()).unwrap_or(1_000_000);
150 let one = bench_requests(1, total);
151 let hundred = bench_requests(100, total);
152 println!("request + 13-byte response, client and server engines, in memory:");
153 println!(" 1 stream at a time {one:8.0} ns/request ({:.2} M req/s on one core)", 1e3 / one);
154 println!(" 100 concurrent streams {hundred:8.0} ns/request ({:.2} M req/s on one core)", 1e3 / hundred);
155 let (ew, dw, ec, dc, len) = bench_hpack(total);
156 println!("HPACK, request list of 6 fields:");
157 println!(" encode warm {ew:6.1} ns ({len} bytes), decode warm {dw:6.1} ns");
158 println!(" encode cold {ec:6.1} ns, decode cold {dc:6.1} ns (fresh tables, includes allocation)");
159 println!("Huffman decode: {:.2} ns/byte", bench_huffman(total));
160}