mod artifact;
mod batch;
pub(in crate::compressor::parallel) mod source;
mod spool;
mod stream;
mod task;
use super::*;
mod fragment;
use crate::compressor::Compressor as SerialCompressor;
use crate::graph::CompressorGraph;
pub(in crate::compressor::parallel) use batch::Batch;
use fragment::FragmentEncoder;
pub(in crate::compressor::parallel) use stream::{
BorrowedStream, OwnedStream, QueueHandle, Stream,
};
pub(in crate::compressor::parallel) use task::Task;
pub(in crate::compressor::parallel) const fn staging_memory_bound(
payload: u64,
segments: u64,
) -> Result<u64, ParallelEncodeError> {
let Some(descriptors) = segments.checked_mul(size_of::<artifact::Descriptor>() as u64) else {
return Err(ParallelEncodeError::SizeOverflow);
};
match payload.checked_add(descriptors) {
Some(bytes) => Ok(bytes),
None => Err(ParallelEncodeError::SizeOverflow),
}
}
#[derive(Clone)]
pub(in crate::compressor::parallel) enum Input<'a> {
Slice(&'a [u8]),
Source(Arc<dyn RandomAccessSource>),
}
impl Input<'_> {
fn len(&self) -> Result<u64, ParallelEncodeError> {
match self {
Self::Slice(s) => Ok(s.len() as u64),
Self::Source(s) => s.len().map_err(ParallelEncodeError::SourceMetadata),
}
}
fn identity(&self) -> Option<SourceIdentity> {
match self {
Self::Slice(_) => None,
Self::Source(s) => s.identity(),
}
}
}
fn serial_compressor(
config: EncoderConfig,
backend: Backend,
graph: Option<&CompressorGraph>,
) -> Result<SerialCompressor, crate::ConfigError> {
#[cfg(not(feature = "zstd"))]
let config = if graph.is_none() {
config
.with_min_stream_size(10)
.with_store_on_expansion(true)
.with_decompression_level(0)
} else {
config
};
let builder = SerialCompressor::builder(config).with_backend(backend);
match graph {
Some(graph) => builder.with_graph(graph.clone()).build(),
None => builder.build(),
}
}
pub(in crate::compressor::parallel) struct Worker {
fragment: FragmentEncoder,
serial: SerialCompressor,
input: Vec<u8>,
}
impl Worker {
fn new(
config: EncoderConfig,
backend: Backend,
graph: Option<&CompressorGraph>,
) -> Result<Self, ParallelConfigError> {
Ok(Self {
fragment: FragmentEncoder::new(config, backend, graph)?,
serial: serial_compressor(config, backend, graph)?,
input: Vec::new(),
})
}
fn retained_bytes(&self) -> usize {
self.fragment.retained_bytes() + self.serial.retained_bytes() + self.input.capacity()
}
}
pub(in crate::compressor::parallel) struct Compressor {
pub(in crate::compressor::parallel) encoder: EncoderConfig,
pub(in crate::compressor::parallel) parallel: ParallelConfig,
backend: Backend,
pub(in crate::compressor::parallel) graph: Option<CompressorGraph>,
pub(in crate::compressor::parallel) workers: Vec<Worker>,
}
impl Compressor {
pub(in crate::compressor::parallel) fn new(
encoder: EncoderConfig,
parallel: ParallelConfig,
backend: Backend,
) -> Result<Self, ParallelConfigError> {
let mut compressor = Self {
encoder,
parallel,
backend,
graph: None,
workers: Vec::new(),
};
compressor.reconfigure(encoder)?;
Ok(compressor)
}
pub(in crate::compressor::parallel) fn reconfigure(
&mut self,
encoder: EncoderConfig,
) -> Result<(), ParallelConfigError> {
if self.encoder != encoder {
self.workers = Vec::new();
self.encoder = encoder;
}
Ok(())
}
fn frame_header(&self, total: u64) -> Result<Vec<u8>, ParallelEncodeError> {
let mut encoder = serial_compressor(self.encoder, self.backend, self.graph.as_ref())
.map_err(ParallelConfigError::from)?;
let mut bytes = Vec::new();
encoder
.frame_header_into(total, &mut bytes)
.map_err(ParallelEncodeError::FrameHeader)?;
Ok(bytes)
}
pub(in crate::compressor::parallel) fn retained_bytes(&self) -> usize {
self.workers
.iter()
.map(Worker::retained_bytes)
.sum::<usize>()
+ self.workers.capacity() * size_of::<Worker>()
}
pub(in crate::compressor::parallel) fn trim(&mut self, policy: ParallelRetentionPolicy) {
match policy {
ParallelRetentionPolicy::ReleaseAll => self.workers = Vec::new(),
ParallelRetentionPolicy::Aggressive => (),
ParallelRetentionPolicy::CurrentPlan => {
self.workers.truncate(self.parallel.max_retained_workers);
self.workers.shrink_to_fit();
}
ParallelRetentionPolicy::Bounded { max_bytes } => {
self.workers.truncate(self.parallel.max_retained_workers);
while self.retained_bytes() > max_bytes && !self.workers.is_empty() {
self.workers.pop();
}
self.workers.shrink_to_fit();
}
}
}
pub(in crate::compressor::parallel) const fn history_len(&self) -> usize {
0
}
fn worker_estimate(&self) -> Result<usize, ParallelEncodeError> {
self.parallel
.segment_size
.get()
.checked_mul(64)
.and_then(|n| n.checked_add(128 << 20))
.ok_or(ParallelEncodeError::SizeOverflow)
}
pub(in crate::compressor::parallel) fn plan(
&self,
len: u64,
config: &BatchConfig,
) -> Result<Plan, ParallelEncodeError> {
let z = self.parallel.segment_size.get() as u64;
let segments = len / z + u64::from(!len.is_multiple_of(z));
let tasks = usize::try_from(segments.min(config.task_count.get() as u64))
.map_err(|_| ParallelEncodeError::SizeOverflow)?;
let serial = len < self.parallel.minimum_parallel_size && len <= z || len == 0;
let staged = len
.checked_mul(2)
.and_then(|n| {
segments
.max(1)
.checked_mul(1024)
.and_then(|s| n.checked_add(s))
})
.ok_or(ParallelEncodeError::SizeOverflow)?;
let descriptors = segments
.checked_mul(size_of::<artifact::Descriptor>() as u64)
.ok_or(ParallelEncodeError::SizeOverflow)?;
let segment = self.parallel.segment_size.get();
let per_worker = self.worker_estimate()?;
let metadata =
usize::try_from(descriptors).map_err(|_| ParallelEncodeError::SizeOverflow)?;
let active = per_worker
.checked_mul(tasks)
.and_then(|n| n.checked_add(metadata))
.and_then(|n| tasks.checked_mul(4096).and_then(|t| n.checked_add(t)))
.and_then(|n| n.checked_add(128 << 10))
.ok_or(ParallelEncodeError::SizeOverflow)?;
let staging_memory = match &config.staging {
Staging::Memory(m) => {
let bytes =
usize::try_from(staged).map_err(|_| ParallelEncodeError::SizeOverflow)?;
let required = usize::try_from(staging_memory_bound(staged, segments)?)
.map_err(|_| ParallelEncodeError::SizeOverflow)?;
if required > m.max_total_bytes {
return Err(ParallelEncodeError::MemoryStagingLimit);
}
bytes
}
Staging::Directory(_) => 0,
};
let aggregate = active
.checked_add(staging_memory)
.and_then(|n| n.checked_add(self.retained_bytes()))
.ok_or(ParallelEncodeError::SizeOverflow)?;
if self
.parallel
.aggregate_memory_limit
.is_some_and(|limit| aggregate > limit)
{
return Err(ParallelEncodeError::WorkerMemoryLimit);
}
Ok(Plan {
tasks,
serial,
segment_size: segment,
history: if serial { 0 } else { self.history_len() as u64 },
estimate: ParallelSizeEstimate {
input_bytes: len,
segment_count: segments,
maximum_staged_bytes: staged,
maximum_final_bytes: staged
.checked_add(64)
.ok_or(ParallelEncodeError::SizeOverflow)?,
estimated_active_workspace_bytes: active,
},
})
}
}
pub(in crate::compressor::parallel) struct Plan {
pub(in crate::compressor::parallel) tasks: usize,
serial: bool,
segment_size: usize,
history: u64,
pub(in crate::compressor::parallel) estimate: ParallelSizeEstimate,
}
impl Plan {
const fn history_start(&self, start: u64) -> u64 {
start.saturating_sub(self.history)
}
fn source_range(&self, segment: u64) -> Result<Range<u64>, ParallelEncodeError> {
let start = segment
.checked_mul(self.segment_size as u64)
.ok_or(ParallelEncodeError::SizeOverflow)?;
let end = start
.saturating_add(self.segment_size as u64)
.min(self.estimate.input_bytes);
if start >= end {
return Err(ParallelEncodeError::FragmentInvariant(
"invalid source range",
));
}
Ok(start..end)
}
}