#![cfg(feature = "async")]
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use futures::TryStreamExt;
use pklib::{
AsyncBatchProcessor, AsyncExplodeReader, AsyncImplodeWriter, AsyncStreamProcessor,
CompressionMode, DictionarySize, StreamOptions,
};
use std::hint::black_box;
use std::io::Cursor;
use std::time::Duration;
use tokio::runtime::Runtime;
fn generate_test_data(size: usize) -> Vec<u8> {
let pattern = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit. ";
let mut data = Vec::with_capacity(size);
while data.len() < size {
data.extend_from_slice(pattern);
}
data.truncate(size);
data
}
fn async_io_overlap_benchmark(c: &mut Criterion) {
let rt = Runtime::new().unwrap();
let mut group = c.benchmark_group("async_io_overlap");
group.measurement_time(Duration::from_secs(10));
for size in [1048576, 10485760].iter() {
let size_label = match *size {
1048576 => "1MB",
10485760 => "10MB",
_ => "unknown",
};
let data = generate_test_data(*size);
let sync_id = BenchmarkId::from_parameter(format!("{size_label}_sync"));
group.throughput(Throughput::Bytes(*size as u64));
group.bench_with_input(sync_id, &data, |b, data| {
b.iter(|| {
let compressed = pklib::implode_bytes(
black_box(data),
CompressionMode::Binary,
DictionarySize::Size4K,
)
.expect("Compression failed");
pklib::explode_bytes(black_box(&compressed)).expect("Decompression failed")
});
});
let async_id = BenchmarkId::from_parameter(format!("{size_label}_async_overlap"));
group.throughput(Throughput::Bytes(*size as u64));
group.bench_with_input(async_id, &data, |b, data| {
b.iter(|| {
rt.block_on(async {
let _cursor = Cursor::new(data);
let mut output = Vec::new();
let mut writer = AsyncImplodeWriter::new(
&mut output,
CompressionMode::Binary,
DictionarySize::Size4K,
)
.expect("Writer creation failed");
for chunk in data.chunks(65536) {
writer
.write_chunk(black_box(chunk))
.await
.expect("Write failed");
}
writer.finish().await.expect("Finish failed");
let compressed_cursor = Cursor::new(&output);
let mut reader =
AsyncExplodeReader::new(compressed_cursor).expect("Reader creation failed");
let mut decompressed = Vec::new();
while let Ok(Some(chunk)) = reader.try_next().await {
decompressed.extend_from_slice(&chunk);
}
decompressed
})
});
});
}
group.finish();
}
fn async_batch_processing_benchmark(c: &mut Criterion) {
let rt = Runtime::new().unwrap();
let mut group = c.benchmark_group("async_batch_processing");
group.measurement_time(Duration::from_secs(15));
let test_cases = vec![
(50, 10240), (20, 51200), ];
for (file_count, file_size) in test_cases {
let files: Vec<Vec<u8>> = (0..file_count)
.map(|_| generate_test_data(file_size))
.collect();
let total_size = file_count * file_size;
let sync_id = BenchmarkId::from_parameter(format!(
"{}files_{}KB_sequential",
file_count,
file_size / 1024
));
group.throughput(Throughput::Bytes(total_size as u64));
group.bench_with_input(sync_id, &files, |b, files| {
b.iter(|| {
let mut results = Vec::new();
for file in files {
let compressed = pklib::implode_bytes(
black_box(file),
CompressionMode::Binary,
DictionarySize::Size4K,
)
.expect("Compression failed");
results.push(compressed);
}
results
});
});
for concurrency in [2, 4].iter() {
let async_id = BenchmarkId::from_parameter(format!(
"{}files_{}KB_concurrent_{}",
file_count,
file_size / 1024,
concurrency
));
group.throughput(Throughput::Bytes(total_size as u64));
group.bench_with_input(async_id, &files, |b, files| {
b.iter(|| {
rt.block_on(async {
let _processor = AsyncBatchProcessor::new().with_concurrency(*concurrency);
let chunks: Vec<_> =
files.chunks(files.len().div_ceil(*concurrency)).collect();
let mut all_results = Vec::new();
for chunk in chunks {
let mut chunk_results = Vec::new();
for file in chunk {
let mut output = Vec::new();
let mut writer = AsyncImplodeWriter::new(
&mut output,
CompressionMode::Binary,
DictionarySize::Size4K,
)
.expect("Writer creation failed");
writer
.write_chunk(black_box(file))
.await
.expect("Write failed");
writer.finish().await.expect("Finish failed");
chunk_results.push(output);
}
all_results.extend(chunk_results);
}
all_results
})
});
});
}
}
group.finish();
}
fn async_memory_efficiency_benchmark(c: &mut Criterion) {
let rt = Runtime::new().unwrap();
let mut group = c.benchmark_group("async_memory_efficiency");
group.measurement_time(Duration::from_secs(8));
let file_size = 10485760; let data = generate_test_data(file_size);
let memory_intensive_id = BenchmarkId::from_parameter("10MB_load_all");
group.throughput(Throughput::Bytes(file_size as u64));
group.bench_with_input(memory_intensive_id, &data, |b, data| {
b.iter(|| {
let input_copy = data.clone(); pklib::implode_bytes(
black_box(&input_copy),
CompressionMode::Binary,
DictionarySize::Size4K,
)
.expect("Compression failed")
});
});
for chunk_size in [65536, 262144].iter() {
let chunk_label = match *chunk_size {
65536 => "64KB",
262144 => "256KB",
_ => "unknown",
};
let streaming_id =
BenchmarkId::from_parameter(format!("10MB_streaming_{chunk_label}_chunks"));
group.throughput(Throughput::Bytes(file_size as u64));
group.bench_with_input(streaming_id, &data, |b, data| {
b.iter(|| {
rt.block_on(async {
let mut output = Vec::new();
let mut writer = AsyncImplodeWriter::with_buffer_size(
&mut output,
CompressionMode::Binary,
DictionarySize::Size4K,
*chunk_size,
)
.expect("Writer creation failed");
for chunk in data.chunks(*chunk_size) {
writer
.write_chunk(black_box(chunk))
.await
.expect("Write failed");
}
writer.finish().await.expect("Finish failed");
output
})
});
});
}
group.finish();
}
fn async_stream_processing_benchmark(c: &mut Criterion) {
let rt = Runtime::new().unwrap();
let mut group = c.benchmark_group("async_stream_processing");
group.measurement_time(Duration::from_secs(8));
let file_size = 5242880; let data = generate_test_data(file_size);
let traditional_id = BenchmarkId::from_parameter("5MB_traditional");
group.throughput(Throughput::Bytes(file_size as u64));
group.bench_with_input(traditional_id, &data, |b, data| {
b.iter(|| {
pklib::implode_bytes(
black_box(data),
CompressionMode::Binary,
DictionarySize::Size4K,
)
.expect("Compression failed")
});
});
let stream_id = BenchmarkId::from_parameter("5MB_stream_processor");
group.throughput(Throughput::Bytes(file_size as u64));
group.bench_with_input(stream_id, &data, |b, data| {
b.iter(|| {
rt.block_on(async {
let input = Cursor::new(data);
let mut output = Vec::new();
let _stats = AsyncStreamProcessor::process_stream(
input,
&mut output,
CompressionMode::Binary,
DictionarySize::Size4K,
StreamOptions::default(),
)
.await
.expect("Stream processing failed");
output
})
});
});
group.finish();
}
fn async_backpressure_benchmark(c: &mut Criterion) {
let rt = Runtime::new().unwrap();
let mut group = c.benchmark_group("async_backpressure");
group.measurement_time(Duration::from_secs(6));
let file_size = 20971520; let data = generate_test_data(file_size);
for memory_limit in [1048576, 4194304].iter() {
let limit_label = match *memory_limit {
1048576 => "1MB_limit",
4194304 => "4MB_limit",
_ => "unknown",
};
let backpressure_id = BenchmarkId::from_parameter(format!("20MB_file_{limit_label}"));
group.throughput(Throughput::Bytes(file_size as u64));
group.bench_with_input(backpressure_id, &data, |b, data| {
b.iter(|| {
rt.block_on(async {
let options = StreamOptions {
chunk_size: memory_limit / 4, buffer_count: 2, memory_limit: *memory_limit,
show_progress: false,
};
let input = Cursor::new(data);
let mut output = Vec::new();
let _stats = AsyncStreamProcessor::process_stream(
input,
&mut output,
CompressionMode::Binary,
DictionarySize::Size4K,
options,
)
.await
.expect("Stream processing failed");
output
})
});
});
}
group.finish();
}
criterion_group!(
async_benches,
async_io_overlap_benchmark,
async_batch_processing_benchmark,
async_memory_efficiency_benchmark,
async_stream_processing_benchmark,
async_backpressure_benchmark
);
criterion_main!(async_benches);