#![cfg(all(feature = "lzma2", feature = "parallel"))]
use std::alloc::{GlobalAlloc, Layout, System};
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
use std::sync::atomic::{AtomicUsize, Ordering};
use zesven::codec::CodecMethod;
use zesven::write::{EntryMeta, WriteOptions, Writer};
use zesven::{ArchivePath, MemoryLimit, Threads};
static LIVE: AtomicUsize = AtomicUsize::new(0);
static PEAK: AtomicUsize = AtomicUsize::new(0);
struct Counted;
impl Counted {
fn note(live: usize) {
PEAK.fetch_max(live, Ordering::Relaxed);
}
}
unsafe impl GlobalAlloc for Counted {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let pointer = unsafe { System.alloc(layout) };
if !pointer.is_null() {
Self::note(LIVE.fetch_add(layout.size(), Ordering::Relaxed) + layout.size());
}
pointer
}
unsafe fn dealloc(&self, pointer: *mut u8, layout: Layout) {
LIVE.fetch_sub(layout.size(), Ordering::Relaxed);
unsafe { System.dealloc(pointer, layout) }
}
unsafe fn realloc(&self, pointer: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
let moved = unsafe { System.realloc(pointer, layout, new_size) };
if !moved.is_null() {
if !std::ptr::eq(moved, pointer) {
Self::note(LIVE.load(Ordering::Relaxed) + new_size);
}
let live = if new_size >= layout.size() {
let by = new_size - layout.size();
LIVE.fetch_add(by, Ordering::Relaxed) + by
} else {
let by = layout.size() - new_size;
LIVE.fetch_sub(by, Ordering::Relaxed) - by
};
Self::note(live);
}
moved
}
}
#[global_allocator]
static ALLOCATOR: Counted = Counted;
fn start_watching() -> usize {
let live = LIVE.load(Ordering::Relaxed);
PEAK.store(live, Ordering::Relaxed);
live
}
fn risen_since(baseline: usize) -> usize {
PEAK.load(Ordering::Relaxed).saturating_sub(baseline)
}
struct NullSink {
position: u64,
end: u64,
}
impl Write for NullSink {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.position += buf.len() as u64;
self.end = self.end.max(self.position);
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
impl Seek for NullSink {
fn seek(&mut self, to: SeekFrom) -> std::io::Result<u64> {
self.position = match to {
SeekFrom::Start(at) => at,
SeekFrom::End(offset) => self.end.saturating_add_signed(offset),
SeekFrom::Current(offset) => self.position.saturating_add_signed(offset),
};
Ok(self.position)
}
}
fn incompressible(len: usize) -> Vec<u8> {
let mut data = Vec::with_capacity(len);
let mut state = 0x2545_F491_4F6C_DD1Du64;
while data.len() < len {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
data.extend_from_slice(&state.to_le_bytes());
}
data.truncate(len);
data
}
struct SliceReader<'a> {
data: &'a [u8],
position: usize,
}
impl Read for SliceReader<'_> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let taken = (self.data.len() - self.position).min(buf.len());
buf[..taken].copy_from_slice(&self.data[self.position..self.position + taken]);
self.position += taken;
Ok(taken)
}
}
fn peak_of(budget: usize, batch: &[Vec<u8>], large: &[&[u8]], method: CodecMethod) -> usize {
let options = WriteOptions::new()
.level(1)
.expect("level")
.method(method)
.memory_limit(MemoryLimit::bytes_or_auto(budget as u64))
.threads(Threads::count_or_single(4));
let mut writer = Writer::create(NullSink {
position: 0,
end: 0,
})
.expect("writer")
.options(options);
let baseline = start_watching();
for (i, data) in batch.iter().enumerate() {
writer
.add_bytes(ArchivePath::new(&format!("s{i}.bin")).expect("path"), data)
.expect("adds");
}
for (i, data) in large.iter().enumerate() {
writer
.add_stream(
ArchivePath::new(&format!("big{i}.bin")).expect("path"),
&mut SliceReader { data, position: 0 },
EntryMeta::file(data.len() as u64),
)
.expect("adds");
}
let _ = writer.finish_into_inner().expect("finishes");
risen_since(baseline)
}
#[test]
fn test_the_overlap_costs_what_it_is_charged_for() {
let budget = 2 << 30;
let batch: Vec<Vec<u8>> = (0..3).map(|_| incompressible(30 << 20)).collect();
let large = incompressible(192 << 20);
let alone = peak_of(budget, &[], &[&large], CodecMethod::Lzma2);
let alongside = peak_of(budget, &batch, &[&large], CodecMethod::Lzma2);
let batch_bytes: usize = batch.iter().map(Vec::len).sum();
let share = budget / 8;
let allowed = alone + share + (16 << 20);
assert!(
alongside <= allowed,
"the same entry peaked at {} MiB alone and {} MiB with a {} MiB batch \
alongside it, over an allowance of {} MiB: something the overlap \
spends is not being charged for",
alone >> 20,
alongside >> 20,
batch_bytes >> 20,
allowed >> 20,
);
let admitted: Vec<Vec<u8>> = (0..3).map(|_| incompressible(12 << 20)).collect();
let admitted_bytes: usize = admitted.iter().map(Vec::len).sum();
let with_admitted = peak_of(budget, &admitted, &[&large], CodecMethod::Lzma2);
assert!(
with_admitted <= allowed,
"a {} MiB batch that was admitted to run alongside took the peak to {} \
MiB against {} MiB for the entry alone, over an allowance of {} MiB",
admitted_bytes >> 20,
with_admitted >> 20,
alone >> 20,
allowed >> 20,
);
let tight = 256 << 20;
let tight_share = tight / 8;
let stored_batch: Vec<Vec<u8>> = (0..3).map(|_| incompressible(2 << 20)).collect();
let stored_alone = peak_of(tight, &[], &[&large], CodecMethod::Copy);
let stored_alongside = peak_of(tight, &stored_batch, &[&large], CodecMethod::Copy);
let stored_allowed = stored_alone + tight_share + (16 << 20);
assert!(
stored_alongside <= stored_allowed,
"stored rather than compressed, the same entry peaked at {} MiB alone \
and {} MiB with a batch alongside it, over an allowance of {} MiB: \
output is reaching the holding area faster than it is being looked at",
stored_alone >> 20,
stored_alongside >> 20,
stored_allowed >> 20,
);
let second = incompressible(192 << 20);
let one_after_another = peak_of(budget, &[], &[&large, &second], CodecMethod::Lzma2);
let tail_allowed = alone + budget / 2 + (16 << 20);
assert!(
one_after_another <= tail_allowed,
"one large entry peaked at {} MiB and two in a row at {} MiB, over an \
allowance of {} MiB: an entry left finishing is holding more than the \
share the entry behind it was told to work around",
alone >> 20,
one_after_another >> 20,
tail_allowed >> 20,
);
assert!(
one_after_another <= budget + (64 << 20),
"two large entries in a row peaked at {} MiB against a {} MiB budget",
one_after_another >> 20,
budget >> 20,
);
let small = 64 << 20;
let lean = incompressible(96 << 20);
let lean_two = incompressible(96 << 20);
let one_lean = peak_of(small, &[], &[&lean], CodecMethod::Lzma2);
let two_lean = peak_of(small, &[], &[&lean, &lean_two], CodecMethod::Lzma2);
eprintln!(
"tight budget: one entry {} MiB, two in a row {} MiB",
one_lean >> 20,
two_lean >> 20
);
assert!(
two_lean <= one_lean + (32 << 20),
"on a {} MiB budget one entry peaked at {} MiB and two in a row at {} \
MiB: an entry left finishing is costing a second encoder on a budget \
that was never told it could have one",
small >> 20,
one_lean >> 20,
two_lean >> 20,
);
round_trips();
}
fn round_trips() {
let data = incompressible(70 << 20);
let options = WriteOptions::new()
.level(1)
.expect("level")
.memory_limit(MemoryLimit::bytes_or_auto(256 << 20))
.threads(Threads::count_or_single(4));
let mut writer = Writer::create(Cursor::new(Vec::new()))
.expect("writer")
.options(options);
writer
.add_bytes(ArchivePath::new("s0.bin").expect("path"), b"small")
.expect("adds");
writer
.add_stream(
ArchivePath::new("big.bin").expect("path"),
&mut SliceReader {
data: &data,
position: 0,
},
EntryMeta::file(data.len() as u64),
)
.expect("adds");
let archive = writer.finish_into_inner().expect("finishes").1.into_inner();
let mut read = zesven::read::Archive::open(Cursor::new(archive)).expect("opens");
assert_eq!(read.extract_to_vec("s0.bin").expect("extracts"), b"small");
assert_eq!(read.extract_to_vec("big.bin").expect("extracts"), data);
}