use crate::block::BlockError;
use crate::block_scan;
use crate::chunk;
pub fn decompress_parallel(data: &[u8]) -> Result<Vec<u8>, BlockError> {
if data.len() < 4 {
return Err(BlockError("input too short for bzip2 header"));
}
if &data[..2] != b"BZ" {
return Err(BlockError("bad bzip2 signature"));
}
if data[2] != b'h' {
return Err(BlockError("only huffman bzip2 supported"));
}
let level = data[3];
if !(b'1'..=b'9').contains(&level) {
return Err(BlockError("invalid bzip2 block size level"));
}
let max_blocksize = 100_000 * (level - b'0') as u32;
let n_threads = std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(4);
let boundaries = block_scan::find_all_blocks_parallel(data, n_threads);
if boundaries.is_empty() {
return Ok(Vec::new());
}
let total_bits = data.len() as u64 * 8;
let n_blocks = boundaries.len();
let n_segments = (n_threads * 4).min(n_blocks).max(1);
let mut segments: Vec<(u64, u64)> = Vec::with_capacity(n_segments);
for i in 0..n_segments {
let first_block = i * n_blocks / n_segments;
let next_first_block = (i + 1) * n_blocks / n_segments;
let start_bit = boundaries[first_block].bit_offset;
let end_bit = if next_first_block < n_blocks {
boundaries[next_first_block].bit_offset
} else {
total_bits
};
segments.push((start_bit, end_bit));
}
let results: Vec<Vec<u8>> = crate::par::par_map(segments.len(), |si| {
let (start_bit, end_bit) = segments[si];
chunk::decode_segment(data, start_bit, end_bit, max_blocksize)
});
let total_size: usize = results.iter().map(|v| v.len()).sum();
let mut output = Vec::with_capacity(total_size);
for seg in results {
output.extend_from_slice(&seg);
}
Ok(output)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parallel_hello() {
let compressed = include_bytes!("../test_data/hello.bz2");
let output = decompress_parallel(compressed).unwrap();
assert_eq!(&output, b"Hello, World!\n");
}
#[test]
fn parallel_liechtenstein() {
let compressed = include_bytes!("../test_data/liechtenstein.osm.bz2");
let output = decompress_parallel(compressed).unwrap();
let sequential = crate::stream::decompress(compressed).unwrap();
assert_eq!(output.len(), sequential.len(), "size mismatch");
assert_eq!(output, sequential, "content mismatch");
}
#[test]
fn parallel_matches_sequential() {
let compressed = include_bytes!("../test_data/liechtenstein.osm.bz2");
let par = decompress_parallel(compressed).unwrap();
let seq = crate::stream::decompress(compressed).unwrap();
assert_eq!(par, seq);
}
}