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