use crate::{
codecs::{Lz4Codec, ZstdCodec},
error::{CompressionError, Result},
metadata::CompressionMetadata,
};
use rayon::prelude::*;
use std::time::Instant;
#[derive(Debug, Clone)]
pub struct ParallelConfig {
pub block_size: usize,
pub num_threads: usize,
}
impl Default for ParallelConfig {
fn default() -> Self {
Self {
block_size: 1024 * 1024, num_threads: 0, }
}
}
impl ParallelConfig {
pub fn with_block_size(block_size: usize) -> Self {
Self {
block_size,
..Default::default()
}
}
pub fn with_threads(mut self, threads: usize) -> Self {
self.num_threads = threads;
self
}
}
pub struct ParallelCompressor {
config: ParallelConfig,
}
impl ParallelCompressor {
pub fn new() -> Self {
Self {
config: ParallelConfig::default(),
}
}
pub fn with_config(config: ParallelConfig) -> Self {
Self { config }
}
pub fn compress_lz4(&self, input: &[u8]) -> Result<(Vec<u8>, CompressionMetadata)> {
let start = Instant::now();
if input.is_empty() {
let metadata = CompressionMetadata::new("lz4-parallel".to_string(), 0, 0);
return Ok((Vec::new(), metadata));
}
let codec = Lz4Codec::new();
let blocks: Vec<&[u8]> = input.chunks(self.config.block_size).collect();
let compressed_blocks: Result<Vec<Vec<u8>>> = blocks
.par_iter()
.map(|block| codec.compress(block))
.collect();
let compressed_blocks = compressed_blocks?;
let mut output = Vec::new();
output.extend_from_slice(&(blocks.len() as u32).to_le_bytes());
output.extend_from_slice(&(input.len() as u64).to_le_bytes());
output.extend_from_slice(&(self.config.block_size as u32).to_le_bytes());
for block in &compressed_blocks {
output.extend_from_slice(&(block.len() as u32).to_le_bytes());
}
for block in compressed_blocks {
output.extend_from_slice(&block);
}
let duration = start.elapsed();
let metadata =
CompressionMetadata::new("lz4-parallel".to_string(), input.len(), output.len())
.with_duration(duration);
Ok((output, metadata))
}
pub fn decompress_lz4(&self, input: &[u8]) -> Result<Vec<u8>> {
if input.is_empty() {
return Ok(Vec::new());
}
if input.len() < 16 {
return Err(CompressionError::InvalidBufferSize(
"Input too small for parallel format".to_string(),
));
}
let num_blocks = u32::from_le_bytes([input[0], input[1], input[2], input[3]]) as usize;
let original_size = u64::from_le_bytes([
input[4], input[5], input[6], input[7], input[8], input[9], input[10], input[11],
]) as usize;
let block_size = u32::from_le_bytes([input[12], input[13], input[14], input[15]]) as usize;
let mut pos = 16;
let mut block_sizes = Vec::with_capacity(num_blocks);
for _ in 0..num_blocks {
if pos + 4 > input.len() {
return Err(CompressionError::InvalidBufferSize(
"Truncated block size".to_string(),
));
}
let size =
u32::from_le_bytes([input[pos], input[pos + 1], input[pos + 2], input[pos + 3]])
as usize;
block_sizes.push(size);
pos += 4;
}
let mut compressed_blocks = Vec::with_capacity(num_blocks);
for &size in &block_sizes {
if pos + size > input.len() {
return Err(CompressionError::InvalidBufferSize(
"Truncated compressed block".to_string(),
));
}
compressed_blocks.push(&input[pos..pos + size]);
pos += size;
}
let codec = Lz4Codec::new();
let decompressed_blocks: Result<Vec<Vec<u8>>> = compressed_blocks
.par_iter()
.enumerate()
.map(|(i, block)| {
let expected_size = if i == num_blocks - 1 {
original_size - (i * block_size)
} else {
block_size
};
codec.decompress(block, Some(expected_size))
})
.collect();
let decompressed_blocks = decompressed_blocks?;
let mut output = Vec::with_capacity(original_size);
for block in decompressed_blocks {
output.extend_from_slice(&block);
}
Ok(output)
}
pub fn compress_zstd(&self, input: &[u8]) -> Result<(Vec<u8>, CompressionMetadata)> {
let start = Instant::now();
if input.is_empty() {
let metadata = CompressionMetadata::new("zstd-parallel".to_string(), 0, 0);
return Ok((Vec::new(), metadata));
}
let codec = ZstdCodec::new();
let blocks: Vec<&[u8]> = input.chunks(self.config.block_size).collect();
let compressed_blocks: Result<Vec<Vec<u8>>> = blocks
.par_iter()
.map(|block| codec.compress(block))
.collect();
let compressed_blocks = compressed_blocks?;
let mut output = Vec::new();
output.extend_from_slice(&(blocks.len() as u32).to_le_bytes());
output.extend_from_slice(&(input.len() as u64).to_le_bytes());
output.extend_from_slice(&(self.config.block_size as u32).to_le_bytes());
for block in &compressed_blocks {
output.extend_from_slice(&(block.len() as u32).to_le_bytes());
}
for block in compressed_blocks {
output.extend_from_slice(&block);
}
let duration = start.elapsed();
let metadata =
CompressionMetadata::new("zstd-parallel".to_string(), input.len(), output.len())
.with_duration(duration);
Ok((output, metadata))
}
pub fn decompress_zstd(&self, input: &[u8]) -> Result<Vec<u8>> {
if input.is_empty() {
return Ok(Vec::new());
}
if input.len() < 16 {
return Err(CompressionError::InvalidBufferSize(
"Input too small for parallel format".to_string(),
));
}
let num_blocks = u32::from_le_bytes([input[0], input[1], input[2], input[3]]) as usize;
let original_size = u64::from_le_bytes([
input[4], input[5], input[6], input[7], input[8], input[9], input[10], input[11],
]) as usize;
let block_size = u32::from_le_bytes([input[12], input[13], input[14], input[15]]) as usize;
let mut pos = 16;
let mut block_sizes = Vec::with_capacity(num_blocks);
for _ in 0..num_blocks {
if pos + 4 > input.len() {
return Err(CompressionError::InvalidBufferSize(
"Truncated block size".to_string(),
));
}
let size =
u32::from_le_bytes([input[pos], input[pos + 1], input[pos + 2], input[pos + 3]])
as usize;
block_sizes.push(size);
pos += 4;
}
let mut compressed_blocks = Vec::with_capacity(num_blocks);
for &size in &block_sizes {
if pos + size > input.len() {
return Err(CompressionError::InvalidBufferSize(
"Truncated compressed block".to_string(),
));
}
compressed_blocks.push(&input[pos..pos + size]);
pos += size;
}
let codec = ZstdCodec::new();
let decompressed_blocks: Result<Vec<Vec<u8>>> = compressed_blocks
.par_iter()
.map(|block| codec.decompress(block, Some(block_size * 2)))
.collect();
let decompressed_blocks = decompressed_blocks?;
let mut output = Vec::with_capacity(original_size);
for block in decompressed_blocks {
output.extend_from_slice(&block);
}
Ok(output)
}
}
impl Default for ParallelCompressor {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parallel_lz4() {
let compressor = ParallelCompressor::new();
let data = vec![42u8; 10_000_000];
let (compressed, metadata) = compressor.compress_lz4(&data).expect("Compression failed");
assert!(compressed.len() < data.len());
assert!(metadata.compression_ratio > 1.0);
let decompressed = compressor
.decompress_lz4(&compressed)
.expect("Decompression failed");
assert_eq!(decompressed, data);
}
#[test]
fn test_parallel_zstd() {
let compressor = ParallelCompressor::new();
let data = vec![42u8; 10_000_000];
let (compressed, metadata) = compressor.compress_zstd(&data).expect("Compression failed");
assert!(compressed.len() < data.len());
assert!(metadata.compression_ratio > 1.0);
let decompressed = compressor
.decompress_zstd(&compressed)
.expect("Decompression failed");
assert_eq!(decompressed, data);
}
}