Source code
Revision control
Copy as Markdown
Other Tools
// This file is part of ICU4X. For terms of use, please see the file
// called LICENSE at the top level of the ICU4X source tree
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use rand::SeedableRng;
use rand_distr::{Alphanumeric, Distribution, Uniform};
use rand_pcg::Lcg64Xsh32;
use std::ops::RangeInclusive;
use zerovec::VarZeroVec;
/// Generates an array of random alphanumeric strings.
///
/// - length = range of lengths for the strings (chosen uniformly at random)
/// - count = number of strings to generate
/// - seed = seed for the PRNG
///
/// Returns a tuple including the vector and a u64 that can be used to seed the next PRNG.
fn random_alphanums(lengths: RangeInclusive<usize>, count: usize, seed: u64) -> (Vec<String>, u64) {
// Lcg64Xsh32 is a small, fast PRNG for reproducible benchmarks.
let mut rng1 = Lcg64Xsh32::seed_from_u64(seed);
let mut rng2 = Lcg64Xsh32::seed_from_u64(rand::Rng::gen(&mut rng1));
let alpha_dist = Alphanumeric;
let len_dist = Uniform::from(lengths);
let string_vec = len_dist
.sample_iter(&mut rng1)
.take(count)
.map(|len| {
(&alpha_dist)
.sample_iter(&mut rng2)
.take(len)
.map(char::from)
.collect::<String>()
})
.collect();
(string_vec, rand::Rng::gen(&mut rng1))
}
fn overview_bench(c: &mut Criterion) {
// Same as vzv/char_count/vzv but with different inputs
let seed = 42;
let (string_vec, _) = random_alphanums(2..=10, 100, seed);
let bytes: Vec<u8> = VarZeroVec::<str>::from(&string_vec).into_bytes();
let vzv = VarZeroVec::<str>::parse_byte_slice(black_box(bytes.as_slice())).unwrap();
c.bench_function("vzv/overview", |b| {
b.iter(|| {
black_box(&vzv)
.iter()
.fold(0, |sum, string| sum + string.chars().count())
});
});
#[cfg(feature = "bench")]
{
char_count_benches(c);
binary_search_benches(c);
vzv_precompute_bench(c);
}
#[cfg(all(feature = "bench", feature = "serde"))]
{
serde_benches(c);
}
}
#[cfg(feature = "bench")]
fn char_count_benches(c: &mut Criterion) {
let seed = 2021;
let (string_vec, _) = random_alphanums(2..=20, 100, seed);
let bytes: Vec<u8> = VarZeroVec::<str>::from(&string_vec).into_bytes();
let vzv = VarZeroVec::<str>::parse_byte_slice(black_box(bytes.as_slice())).unwrap();
// *** Count chars in vec of 100 strings ***
c.bench_function("vzv/char_count/slice", |b| {
b.iter(|| {
black_box(&string_vec)
.iter()
.fold(0, |sum, string| sum + string.chars().count())
});
});
// *** Count chars in vec of 100 strings ***
c.bench_function("vzv/char_count/vzv", |b| {
b.iter(|| {
black_box(&vzv)
.iter()
.fold(0, |sum, string| sum + string.chars().count())
});
});
}
#[cfg(feature = "bench")]
fn binary_search_benches(c: &mut Criterion) {
let seed = 2021;
let (string_vec, seed) = random_alphanums(2..=20, 500, seed);
let (needles, _) = random_alphanums(2..=20, 10, seed);
let bytes: Vec<u8> = VarZeroVec::<str>::from(&string_vec).into_bytes();
let vzv = VarZeroVec::<str>::parse_byte_slice(black_box(bytes.as_slice())).unwrap();
let single_needle = "lmnop".to_owned();
// *** Binary search vec of 500 strings 10 times ***
c.bench_function("vzv/binary_search/slice", |b| {
b.iter(|| {
black_box(&needles)
.iter()
.map(|needle| black_box(&string_vec).binary_search(needle))
.filter(|r| r.is_ok())
.count()
});
});
// *** Binary search vec of 500 strings 10 times ***
c.bench_function("vzv/binary_search/vzv", |b| {
b.iter(|| {
black_box(&needles)
.iter()
.map(|needle| black_box(&vzv).binary_search(needle))
.filter(|r| r.is_ok())
.count()
});
});
c.bench_function("vzv/binary_search/single/slice", |b| {
b.iter(|| black_box(&string_vec).binary_search(black_box(&single_needle)));
});
c.bench_function("vzv/binary_search/single/vzv", |b| {
b.iter(|| black_box(&vzv).binary_search(black_box(&single_needle)));
});
}
#[cfg(all(feature = "bench", feature = "serde"))]
fn serde_benches(c: &mut Criterion) {
let seed = 2021;
let (string_vec, _) = random_alphanums(2..=20, 100, seed);
let bincode_vec = bincode::serialize(&string_vec).unwrap();
let vzv: VarZeroVec<str> = VarZeroVec::from(&*string_vec);
let bincode_vzv = bincode::serialize(&vzv).unwrap();
// *** Deserialize vec of 100 strings ***
c.bench_function("vzv/deserialize/string/vec_owned", |b| {
b.iter(|| bincode::deserialize::<Vec<String>>(black_box(&bincode_vec)));
});
// *** Deserialize vec of 100 strings ***
c.bench_function("vzv/deserialize/string/vec_borrowed", |b| {
b.iter(|| bincode::deserialize::<Vec<&str>>(black_box(&bincode_vec)));
});
// *** Deserialize vec of 100 strings ***
c.bench_function("vzv/deserialize/string/vzv", |b| {
b.iter(|| bincode::deserialize::<VarZeroVec<str>>(black_box(&bincode_vzv)));
});
}
#[cfg(feature = "bench")]
// Testing differences between operating on slices with precomputed/non-precomputed indexing info
fn vzv_precompute_bench(c: &mut Criterion) {
let seed = 2021;
let (string_vec, seed) = random_alphanums(2..=20, 500, seed);
let (needles, _) = random_alphanums(2..=20, 10, seed);
let bytes: Vec<u8> = VarZeroVec::<str>::from(&string_vec).into_bytes();
let vzv = VarZeroVec::<str>::parse_byte_slice(black_box(bytes.as_slice())).unwrap();
let borrowed = vzv.as_components();
let slice = vzv.as_slice();
let single_needle = "lmnop";
c.bench_function("vzv_precompute/get/precomputed", |b| {
b.iter(|| black_box(&borrowed).get(100));
});
c.bench_function("vzv_precompute/get/slice", |b| {
b.iter(|| black_box(&slice).get(100));
});
c.bench_function("vzv_precompute/search/precomputed", |b| {
b.iter(|| black_box(&borrowed).binary_search(single_needle));
});
c.bench_function("vzv_precompute/search/slice", |b| {
b.iter(|| black_box(&slice).binary_search(single_needle));
});
c.bench_function("vzv_precompute/search_multi/precomputed", |b| {
b.iter(|| {
black_box(&needles)
.iter()
.map(|needle| black_box(&borrowed).binary_search(needle))
.filter(|r| r.is_ok())
.count()
});
});
c.bench_function("vzv_precompute/search_multi/slice", |b| {
b.iter(|| {
black_box(&needles)
.iter()
.map(|needle| black_box(&slice).binary_search(needle))
.filter(|r| r.is_ok())
.count()
});
});
}
criterion_group!(benches, overview_bench,);
criterion_main!(benches);