Skip to main content

bench_h2/
bench_h2.rs

1//! In-memory cost of HTTP/2 through the sans-io client and server engines.
2//!
3//! `cargo run -p simoom-h2 --release --example bench_h2 [iterations]`
4//!
5//! Measures, per request: a small GET (5 request fields) answered with a 13-byte body
6//! (`content-type` plus DATA with END_STREAM), driven through both state machines with no
7//! sockets, one stream at a time and 100 concurrent streams; then HPACK encode and decode of
8//! the request header list with a warm and a cold dynamic table, and Huffman decoding.
9
10use 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
38/// One batch: `streams` requests out, responses back. Returns body bytes received.
39fn 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    // warm up (tables, buffers)
70    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    // cold: a fresh context every time (the first request on a connection)
115    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}