#![forbid(unsafe_code)]
use crate::core::representation::Representation;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct CostBreakdown {
pub logical_bytes: u64,
pub descriptor_bytes: u64,
pub model_bytes: u64,
pub object_payload_bytes: u64,
pub residual_bytes: u64,
pub seed_state_bytes: u64,
pub reference_bytes: u64,
pub configurational_bytes: u64,
pub integrity_bytes: u64,
pub gc_overhead_bytes: u64,
pub read_cycles: u64,
pub write_cycles: u64,
pub dependent_reads: u32,
pub depth: u8,
}
impl CostBreakdown {
pub const fn persisted_bytes(&self) -> u64 {
self.descriptor_bytes
.saturating_add(self.model_bytes)
.saturating_add(self.object_payload_bytes)
.saturating_add(self.residual_bytes)
.saturating_add(self.seed_state_bytes)
.saturating_add(self.reference_bytes)
.saturating_add(self.configurational_bytes)
.saturating_add(self.integrity_bytes)
}
pub const fn persisted_with_gc(&self) -> u64 {
self.persisted_bytes()
.saturating_add(self.gc_overhead_bytes)
}
pub fn total(&self, policy: &Policy) -> u128 {
let persisted = self.persisted_with_gc() as u128;
let read = ((policy.lambda_read as u128) * (self.read_cycles as u128)) / 1024;
let write = ((policy.lambda_write as u128) * (self.write_cycles as u128)) / 1024;
let io = (policy.lambda_io as u128) * (self.dependent_reads as u128);
let depth = (policy.lambda_depth as u128) * (self.depth as u128);
persisted
.saturating_add(read)
.saturating_add(write)
.saturating_add(io)
.saturating_add(depth)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, clap::ValueEnum)]
pub enum PolicyMode {
Capacity,
Balanced,
Latency,
Archive,
Ram,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Policy {
pub lambda_read: u64,
pub lambda_write: u64,
pub lambda_io: u64,
pub lambda_depth: u64,
}
impl Default for Policy {
fn default() -> Self {
Self::balanced()
}
}
impl Policy {
pub const fn balanced() -> Self {
Self {
lambda_read: 8,
lambda_write: 8,
lambda_io: 512,
lambda_depth: 1024,
}
}
pub const fn mode(mode: PolicyMode) -> Self {
match mode {
PolicyMode::Capacity => Self {
lambda_read: 2,
lambda_write: 2,
lambda_io: 256,
lambda_depth: 512,
},
PolicyMode::Balanced => Self::balanced(),
PolicyMode::Latency => Self {
lambda_read: 64,
lambda_write: 16,
lambda_io: 4096,
lambda_depth: 8192,
},
PolicyMode::Archive => Self {
lambda_read: 1,
lambda_write: 1,
lambda_io: 64,
lambda_depth: 256,
},
PolicyMode::Ram => Self {
lambda_read: 32,
lambda_write: 32,
lambda_io: 2048,
lambda_depth: 4096,
},
}
}
}
pub fn estimated_read_cycles(rep: &Representation) -> u64 {
let len = rep.len();
match rep {
Representation::Zero { .. } | Representation::Fill { .. } => len / 8,
Representation::Inline { .. } | Representation::Raw { .. } => len,
Representation::Rans { .. } => len * 4,
Representation::ExactRef { .. } => len,
Representation::BaseResidual { .. } => len * 2,
Representation::Sparse { .. } => len / 8 + 4,
Representation::Palette { .. } => len,
Representation::Periodic { .. } => len / 8,
Representation::EntropyRef { .. } => len * 8,
Representation::Permutation { .. } => len * 4,
}
}
pub fn estimated_write_cycles(rep: &Representation) -> u64 {
let len = rep.len();
match rep {
Representation::Zero { .. } | Representation::Fill { .. } => len / 8,
Representation::Inline { .. } | Representation::Raw { .. } => len,
Representation::Rans { .. } => len * 6,
Representation::ExactRef { .. } => 32,
Representation::BaseResidual { .. } => len * 3,
Representation::Sparse { .. } => len / 8 + 8,
Representation::Palette { .. } => len * 2,
Representation::Periodic { .. } => len / 8,
Representation::EntropyRef { .. } => len * 10,
Representation::Permutation { .. } => len * 8,
}
}
pub fn dependent_reads(rep: &Representation) -> u32 {
match rep {
Representation::Zero { .. }
| Representation::Fill { .. }
| Representation::Inline { .. }
| Representation::Periodic { .. }
| Representation::Permutation { .. } => 0,
Representation::Raw { .. } | Representation::Rans { .. } => 1,
Representation::ExactRef { .. } => 1,
Representation::BaseResidual { residual, .. } => match residual {
crate::core::representation::Residual::RansCoded { .. } => 3, _ => 1,
},
Representation::Sparse { .. } | Representation::Palette { .. } => 0,
Representation::EntropyRef { residual, .. } => match residual {
crate::core::representation::Residual::RansCoded { .. } => 2, _ => 0,
},
}
}
pub fn reference_depth(rep: &Representation) -> u8 {
match rep {
Representation::ExactRef { .. } => 1,
Representation::BaseResidual { .. } => 1,
_ => 0,
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct ByteSplit {
pub residual: u64,
pub reference: u64,
pub configurational: u64,
pub seed_state: u64,
}
pub fn estimate(rep: &Representation, split: &ByteSplit, model_bytes: u64) -> CostBreakdown {
let encoded = rep.encoded_size();
let descriptor = encoded
.saturating_sub(split.residual)
.saturating_sub(split.reference)
.saturating_sub(split.configurational)
.saturating_sub(split.seed_state);
CostBreakdown {
logical_bytes: rep.len(),
descriptor_bytes: descriptor,
model_bytes,
object_payload_bytes: 0,
residual_bytes: split.residual,
seed_state_bytes: split.seed_state,
reference_bytes: split.reference,
configurational_bytes: split.configurational,
integrity_bytes: 4, gc_overhead_bytes: 0,
read_cycles: estimated_read_cycles(rep),
write_cycles: estimated_write_cycles(rep),
dependent_reads: dependent_reads(rep),
depth: reference_depth(rep),
}
}
#[cfg(test)]
mod split_tests {
use super::*;
use crate::core::representation::{Edit, Residual};
#[test]
fn split_sums_match_encoded_plus_model() {
let rep = Representation::Sparse {
k: 3,
rank: 10,
literals: vec![1, 2, 3],
len: 64,
};
let split = ByteSplit {
residual: 3,
configurational: 16,
..Default::default()
};
let c = estimate(&rep, &split, 0);
assert_eq!(c.persisted_bytes(), rep.encoded_size() + 4);
}
#[test]
fn residual_split_rules() {
let res = Residual::XorSparse {
len: 64,
edits: vec![Edit { pos: 1, val: 2 }, Edit { pos: 3, val: 4 }],
};
let rep = Representation::BaseResidual {
base: crate::core::extent::ChunkId::ZERO,
base_len: 64,
residual: res.clone(),
len: 64,
};
let split = ByteSplit {
residual: 10,
reference: 32,
..Default::default()
};
let c = estimate(&rep, &split, 0);
assert_eq!(c.persisted_bytes(), rep.encoded_size() + 4);
assert_eq!(c.reference_bytes, 32);
assert_eq!(c.residual_bytes, 10);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn raw_dominates_random_under_capacity() {
let raw = CostBreakdown {
logical_bytes: 65536,
descriptor_bytes: 9,
object_payload_bytes: 65536,
..Default::default()
};
let generated = CostBreakdown {
logical_bytes: 65536,
descriptor_bytes: 9,
seed_state_bytes: 24,
residual_bytes: 65536,
..Default::default()
};
let p = Policy::mode(PolicyMode::Capacity);
assert!(generated.persisted_bytes() > raw.persisted_bytes());
assert!(generated.total(&p) > raw.total(&p));
}
#[test]
fn latency_policy_prefers_cheap_reads() {
let raw = CostBreakdown {
logical_bytes: 65536,
descriptor_bytes: 40,
read_cycles: estimated_read_cycles(&Representation::Raw {
obj: crate::core::extent::ChunkId::ZERO,
len: 65536,
}),
..Default::default()
};
assert_eq!(raw.read_cycles, 65536);
assert!(
Policy::mode(PolicyMode::Latency).lambda_read
> Policy::mode(PolicyMode::Capacity).lambda_read
);
}
}