#[cfg(not(feature = "no_std"))]
mod parallel {
use criterion::{BenchmarkId, Criterion, Throughput};
use mzstd::compressor::parallel::{
ParallelCompressor, ParallelConfig, ParallelEncoderStatus, ParallelTaskQueue, SegmentSize,
TaskCount,
};
use mzstd::{Compressor, EncoderConfig, InputSize, Level, Operation};
use std::hint::black_box;
use zstd_ffi::corpus;
use zstd_ffi::oracle::{self, Params};
fn with_executors<T>(
queue: ParallelTaskQueue,
executors: usize,
body: impl FnOnce() -> T,
) -> T {
std::thread::scope(|scope| {
for _ in 0..executors {
let queue = queue.clone();
scope.spawn(move || queue.into_iter().for_each(|task| task.run()));
}
drop(queue);
body()
})
}
fn parallel_session(compressor: &mut ParallelCompressor, input: &[u8], out: &mut Vec<u8>) {
out.clear();
let stream = InputSize::Exact(input.len() as u64).into();
let window = TaskCount::try_from(PARALLEL_TASKS).expect("a window");
let (mut session, queue) = compressor.start(stream, window);
with_executors(queue, PARALLEL_TASKS, || {
let mut buffer = vec![0u8; 64 << 10];
let mut position = 0;
loop {
let end = (position + (64 << 10)).min(input.len());
let operation = if end == input.len() {
Operation::End
} else {
Operation::Continue
};
let progress = session
.process(&input[position..end], &mut buffer, operation)
.expect("compresses");
position += progress.consumed;
out.extend_from_slice(&buffer[..progress.produced]);
match progress.status {
ParallelEncoderStatus::Finished => break,
ParallelEncoderStatus::Pending => session.wait().expect("waits"),
_ => {}
}
}
});
}
fn parallel_writer(compressor: &mut ParallelCompressor, input: &[u8], out: &mut Vec<u8>) {
use std::io::Write;
out.clear();
let stream = InputSize::Exact(input.len() as u64).into();
let window = TaskCount::try_from(PARALLEL_TASKS).expect("a window");
let (mut sink, queue) = compressor.writer(out, stream, window);
with_executors(queue, PARALLEL_TASKS, || {
for piece in input.chunks(64 << 10) {
sink.write_all(piece).expect("writes");
}
sink.try_finish().expect("finishes");
});
}
fn parallel_batch(compressor: &mut ParallelCompressor, input: &[u8], out: &mut Vec<u8>) {
out.clear();
let tasks = TaskCount::try_from(PARALLEL_TASKS).expect("a task count");
let mut batch = compressor.prepare_slice(input, tasks);
std::thread::scope(|scope| {
for task in batch.take_tasks() {
scope.spawn(move || task.run());
}
});
batch.finish_into(out).expect("compresses");
}
const PARALLEL_TASKS: usize = 4;
pub fn parallel(c: &mut Criterion) {
let input = corpus::mixed(16 << 20, 11);
let mut group = c.benchmark_group("parallel/mixed-16MiB");
group.throughput(Throughput::Bytes(input.len() as u64));
for level in [1, 3] {
let config =
EncoderConfig::default().with_level(Level::try_from(level).expect("level"));
let segments = ParallelConfig::default()
.with_segment_size(SegmentSize::try_from(1 << 20).expect("a segment size"));
let mut compressor =
ParallelCompressor::new(config, segments).expect("a configuration");
let mut batch = Vec::new();
parallel_batch(&mut compressor, &input, &mut batch);
let mut out = Vec::new();
parallel_session(&mut compressor, &input, &mut out);
assert_eq!(out, batch, "session, level {level}");
parallel_writer(&mut compressor, &input, &mut out);
assert_eq!(out, batch, "writer, level {level}");
assert_eq!(
oracle::decompress(&batch, input.len()).expect("decodes"),
input
);
let params = Params::level(level);
let theirs = oracle::compress2(&input, ¶ms).expect("reference");
println!(
"parallel level {level}: {} -> {} bytes ({:.4}); zstd serial {} bytes ({:.4})",
input.len(),
batch.len(),
batch.len() as f64 / input.len() as f64,
theirs.len(),
theirs.len() as f64 / input.len() as f64
);
group.bench_function(BenchmarkId::new("mzstd-batch", level), |b| {
b.iter(|| parallel_batch(&mut compressor, black_box(&input), &mut out));
});
group.bench_function(BenchmarkId::new("mzstd-session", level), |b| {
b.iter(|| parallel_session(&mut compressor, black_box(&input), &mut out));
});
group.bench_function(BenchmarkId::new("mzstd-writer", level), |b| {
b.iter(|| parallel_writer(&mut compressor, black_box(&input), &mut out));
});
let mut cctx = oracle::Encoder::new(¶ms);
let mut dst = vec![0u8; Compressor::max_compressed_size(input.len()).expect("a bound")];
group.bench_function(BenchmarkId::new("zstd-serial", level), |b| {
b.iter(|| cctx.compress_into(black_box(&input), &mut dst));
});
}
group.finish();
}
}
#[cfg(not(feature = "no_std"))]
criterion::criterion_group! {
name = benches;
config = criterion::Criterion::default().sample_size(10);
targets = parallel::parallel
}
#[cfg(not(feature = "no_std"))]
criterion::criterion_main!(benches);
#[cfg(feature = "no_std")]
fn main() {}