1 #[macro_use]
2 extern crate criterion;
3 
4 use base64::{
5     display,
6     engine::{general_purpose::STANDARD, Engine},
7     write,
8 };
9 use criterion::{black_box, Bencher, BenchmarkId, Criterion, Throughput};
10 use rand::{Rng, SeedableRng};
11 use std::io::{self, Read, Write};
12 
do_decode_bench(b: &mut Bencher, &size: &usize)13 fn do_decode_bench(b: &mut Bencher, &size: &usize) {
14     let mut v: Vec<u8> = Vec::with_capacity(size * 3 / 4);
15     fill(&mut v);
16     let encoded = STANDARD.encode(&v);
17 
18     b.iter(|| {
19         let orig = STANDARD.decode(&encoded);
20         black_box(&orig);
21     });
22 }
23 
do_decode_bench_reuse_buf(b: &mut Bencher, &size: &usize)24 fn do_decode_bench_reuse_buf(b: &mut Bencher, &size: &usize) {
25     let mut v: Vec<u8> = Vec::with_capacity(size * 3 / 4);
26     fill(&mut v);
27     let encoded = STANDARD.encode(&v);
28 
29     let mut buf = Vec::new();
30     b.iter(|| {
31         STANDARD.decode_vec(&encoded, &mut buf).unwrap();
32         black_box(&buf);
33         buf.clear();
34     });
35 }
36 
do_decode_bench_slice(b: &mut Bencher, &size: &usize)37 fn do_decode_bench_slice(b: &mut Bencher, &size: &usize) {
38     let mut v: Vec<u8> = Vec::with_capacity(size * 3 / 4);
39     fill(&mut v);
40     let encoded = STANDARD.encode(&v);
41 
42     let mut buf = vec![0; size];
43     b.iter(|| {
44         STANDARD.decode_slice(&encoded, &mut buf).unwrap();
45         black_box(&buf);
46     });
47 }
48 
do_decode_bench_stream(b: &mut Bencher, &size: &usize)49 fn do_decode_bench_stream(b: &mut Bencher, &size: &usize) {
50     let mut v: Vec<u8> = Vec::with_capacity(size * 3 / 4);
51     fill(&mut v);
52     let encoded = STANDARD.encode(&v);
53 
54     let mut buf = vec![0; size];
55     buf.truncate(0);
56 
57     b.iter(|| {
58         let mut cursor = io::Cursor::new(&encoded[..]);
59         let mut decoder = base64::read::DecoderReader::new(&mut cursor, &STANDARD);
60         decoder.read_to_end(&mut buf).unwrap();
61         buf.clear();
62         black_box(&buf);
63     });
64 }
65 
do_encode_bench(b: &mut Bencher, &size: &usize)66 fn do_encode_bench(b: &mut Bencher, &size: &usize) {
67     let mut v: Vec<u8> = Vec::with_capacity(size);
68     fill(&mut v);
69     b.iter(|| {
70         let e = STANDARD.encode(&v);
71         black_box(&e);
72     });
73 }
74 
do_encode_bench_display(b: &mut Bencher, &size: &usize)75 fn do_encode_bench_display(b: &mut Bencher, &size: &usize) {
76     let mut v: Vec<u8> = Vec::with_capacity(size);
77     fill(&mut v);
78     b.iter(|| {
79         let e = format!("{}", display::Base64Display::new(&v, &STANDARD));
80         black_box(&e);
81     });
82 }
83 
do_encode_bench_reuse_buf(b: &mut Bencher, &size: &usize)84 fn do_encode_bench_reuse_buf(b: &mut Bencher, &size: &usize) {
85     let mut v: Vec<u8> = Vec::with_capacity(size);
86     fill(&mut v);
87     let mut buf = String::new();
88     b.iter(|| {
89         STANDARD.encode_string(&v, &mut buf);
90         buf.clear();
91     });
92 }
93 
do_encode_bench_slice(b: &mut Bencher, &size: &usize)94 fn do_encode_bench_slice(b: &mut Bencher, &size: &usize) {
95     let mut v: Vec<u8> = Vec::with_capacity(size);
96     fill(&mut v);
97     // conservative estimate of encoded size
98     let mut buf = vec![0; v.len() * 2];
99     b.iter(|| STANDARD.encode_slice(&v, &mut buf).unwrap());
100 }
101 
do_encode_bench_stream(b: &mut Bencher, &size: &usize)102 fn do_encode_bench_stream(b: &mut Bencher, &size: &usize) {
103     let mut v: Vec<u8> = Vec::with_capacity(size);
104     fill(&mut v);
105     let mut buf = Vec::with_capacity(size * 2);
106 
107     b.iter(|| {
108         buf.clear();
109         let mut stream_enc = write::EncoderWriter::new(&mut buf, &STANDARD);
110         stream_enc.write_all(&v).unwrap();
111         stream_enc.flush().unwrap();
112     });
113 }
114 
do_encode_bench_string_stream(b: &mut Bencher, &size: &usize)115 fn do_encode_bench_string_stream(b: &mut Bencher, &size: &usize) {
116     let mut v: Vec<u8> = Vec::with_capacity(size);
117     fill(&mut v);
118 
119     b.iter(|| {
120         let mut stream_enc = write::EncoderStringWriter::new(&STANDARD);
121         stream_enc.write_all(&v).unwrap();
122         stream_enc.flush().unwrap();
123         let _ = stream_enc.into_inner();
124     });
125 }
126 
do_encode_bench_string_reuse_buf_stream(b: &mut Bencher, &size: &usize)127 fn do_encode_bench_string_reuse_buf_stream(b: &mut Bencher, &size: &usize) {
128     let mut v: Vec<u8> = Vec::with_capacity(size);
129     fill(&mut v);
130 
131     let mut buf = String::new();
132     b.iter(|| {
133         buf.clear();
134         let mut stream_enc = write::EncoderStringWriter::from_consumer(&mut buf, &STANDARD);
135         stream_enc.write_all(&v).unwrap();
136         stream_enc.flush().unwrap();
137         let _ = stream_enc.into_inner();
138     });
139 }
140 
fill(v: &mut Vec<u8>)141 fn fill(v: &mut Vec<u8>) {
142     let cap = v.capacity();
143     // weak randomness is plenty; we just want to not be completely friendly to the branch predictor
144     let mut r = rand::rngs::SmallRng::from_entropy();
145     while v.len() < cap {
146         v.push(r.gen::<u8>());
147     }
148 }
149 
150 const BYTE_SIZES: [usize; 5] = [3, 50, 100, 500, 3 * 1024];
151 
152 // Benchmarks over these byte sizes take longer so we will run fewer samples to
153 // keep the benchmark runtime reasonable.
154 const LARGE_BYTE_SIZES: [usize; 3] = [3 * 1024 * 1024, 10 * 1024 * 1024, 30 * 1024 * 1024];
155 
encode_benchmarks(c: &mut Criterion, label: &str, byte_sizes: &[usize])156 fn encode_benchmarks(c: &mut Criterion, label: &str, byte_sizes: &[usize]) {
157     let mut group = c.benchmark_group(label);
158     group
159         .warm_up_time(std::time::Duration::from_millis(500))
160         .measurement_time(std::time::Duration::from_secs(3));
161 
162     for size in byte_sizes {
163         group
164             .throughput(Throughput::Bytes(*size as u64))
165             .bench_with_input(BenchmarkId::new("encode", size), size, do_encode_bench)
166             .bench_with_input(
167                 BenchmarkId::new("encode_display", size),
168                 size,
169                 do_encode_bench_display,
170             )
171             .bench_with_input(
172                 BenchmarkId::new("encode_reuse_buf", size),
173                 size,
174                 do_encode_bench_reuse_buf,
175             )
176             .bench_with_input(
177                 BenchmarkId::new("encode_slice", size),
178                 size,
179                 do_encode_bench_slice,
180             )
181             .bench_with_input(
182                 BenchmarkId::new("encode_reuse_buf_stream", size),
183                 size,
184                 do_encode_bench_stream,
185             )
186             .bench_with_input(
187                 BenchmarkId::new("encode_string_stream", size),
188                 size,
189                 do_encode_bench_string_stream,
190             )
191             .bench_with_input(
192                 BenchmarkId::new("encode_string_reuse_buf_stream", size),
193                 size,
194                 do_encode_bench_string_reuse_buf_stream,
195             );
196     }
197 
198     group.finish();
199 }
200 
decode_benchmarks(c: &mut Criterion, label: &str, byte_sizes: &[usize])201 fn decode_benchmarks(c: &mut Criterion, label: &str, byte_sizes: &[usize]) {
202     let mut group = c.benchmark_group(label);
203 
204     for size in byte_sizes {
205         group
206             .warm_up_time(std::time::Duration::from_millis(500))
207             .measurement_time(std::time::Duration::from_secs(3))
208             .throughput(Throughput::Bytes(*size as u64))
209             .bench_with_input(BenchmarkId::new("decode", size), size, do_decode_bench)
210             .bench_with_input(
211                 BenchmarkId::new("decode_reuse_buf", size),
212                 size,
213                 do_decode_bench_reuse_buf,
214             )
215             .bench_with_input(
216                 BenchmarkId::new("decode_slice", size),
217                 size,
218                 do_decode_bench_slice,
219             )
220             .bench_with_input(
221                 BenchmarkId::new("decode_stream", size),
222                 size,
223                 do_decode_bench_stream,
224             );
225     }
226 
227     group.finish();
228 }
229 
bench(c: &mut Criterion)230 fn bench(c: &mut Criterion) {
231     encode_benchmarks(c, "encode_small_input", &BYTE_SIZES[..]);
232     encode_benchmarks(c, "encode_large_input", &LARGE_BYTE_SIZES[..]);
233     decode_benchmarks(c, "decode_small_input", &BYTE_SIZES[..]);
234     decode_benchmarks(c, "decode_large_input", &LARGE_BYTE_SIZES[..]);
235 }
236 
237 criterion_group!(benches, bench);
238 criterion_main!(benches);
239