#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct StreamClass {
pub size: usize,
pub count: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StreamLayout {
pub basic: Vec<StreamClass>,
pub recursive: StreamClass,
pub unused: usize,
}
const RECURSIVE_STREAM_FLOOR: usize = 1;
const LARGE_HEAVY_SLACK: f64 = 1.2;
impl StreamLayout {
pub fn n_basic_streams(&self) -> usize {
self.basic.iter().map(|c| c.count).sum()
}
pub fn basic_stream_sizes(&self) -> Vec<usize> {
self.basic.iter().flat_map(|c| std::iter::repeat_n(c.size, c.count)).collect()
}
}
fn makespan_floor(basic: &[StreamClass], basic_sizes_desc: &[usize]) -> u128 {
let mut airs: Vec<usize> = basic_sizes_desc.to_vec();
airs.sort_unstable_by(|a, b| b.cmp(a));
let (mut work, mut worst) = (0u128, 0u128);
for air in airs {
work += air as u128;
let streams: u128 = basic.iter().filter(|c| air <= c.size).map(|c| c.count as u128).sum();
worst = worst.max((work << 20) / streams);
}
worst
}
pub fn plan_stream_layout(
budget: usize,
basic_sizes_desc: &[usize],
class_floor: usize,
recursive_size: usize,
max_basic_streams: usize,
max_recursive_streams: usize,
) -> Option<StreamLayout> {
if max_basic_streams == 0 {
return None;
}
let mut sizes: Vec<usize> = basic_sizes_desc.iter().map(|&s| s.max(class_floor)).collect();
sizes.sort_unstable_by(|a, b| b.cmp(a));
sizes.dedup();
let &large = sizes.first()?;
if large == 0 || budget < large {
return None;
}
let for_extra_streams = budget.saturating_sub(recursive_size * RECURSIVE_STREAM_FLOOR.min(max_recursive_streams));
let hosted_work = |size: usize| basic_sizes_desc.iter().filter(|&&air| air <= size).sum::<usize>() as u128;
let regular = sizes
.iter()
.skip(1)
.copied()
.filter(|&s| large + s <= for_extra_streams)
.max_by(|&a, &b| (hosted_work(a) * b as u128).cmp(&(hosted_work(b) * a as u128)).then(a.cmp(&b)))
.unwrap_or(0);
let carves: Vec<(u128, StreamLayout)> = (1..=max_basic_streams)
.filter_map(|n_large| {
let spent = n_large * large;
if spent > budget || (n_large > 1 && spent > for_extra_streams) {
return None;
}
let n_regular = match regular {
0 => 0,
size => (max_basic_streams - n_large).min(for_extra_streams.saturating_sub(spent) / size),
};
let mut basic = vec![StreamClass { size: large, count: n_large }];
if n_regular > 0 {
basic.push(StreamClass { size: regular, count: n_regular });
}
let remaining = budget - spent - n_regular * regular;
let n_recursive = match recursive_size {
0 => 0,
size => max_recursive_streams.min(remaining / size),
};
Some((
makespan_floor(&basic, basic_sizes_desc),
StreamLayout {
basic,
recursive: StreamClass { size: recursive_size, count: n_recursive },
unused: remaining - n_recursive * recursive_size,
},
))
})
.collect();
let best = carves.iter().map(|(floor, _)| *floor).min()?;
carves
.into_iter()
.filter(|(floor, _)| *floor as f64 <= best as f64 * LARGE_HEAVY_SLACK)
.max_by_key(|(_, layout)| (layout.basic[0].count, layout.recursive.count))
.map(|(_, layout)| layout)
}
#[cfg(test)]
mod tests {
use super::*;
const ZISK_BASIC_GB: &[f64] = &[14.57, 6.45, 6.26, 5.82, 5.57, 4.74, 4.52, 4.43, 3.82, 2.63, 1.47, 0.75];
const ZISK_COMPRESSOR_GB: f64 = 6.24;
const ZISK_RECURSIVE_GB: f64 = 1.33;
const ZISK_BUDGET_GB: f64 = 25.23;
fn gb(v: f64) -> usize {
(v * (1 << 30) as f64 / 8.0) as usize
}
fn zisk_layout(budget_gb: f64, max_basic: usize, max_recursive: usize) -> StreamLayout {
let sizes: Vec<usize> = ZISK_BASIC_GB.iter().copied().map(gb).collect();
plan_stream_layout(
gb(budget_gb),
&sizes,
gb(ZISK_COMPRESSOR_GB),
gb(ZISK_RECURSIVE_GB),
max_basic,
max_recursive,
)
.expect("zisk budget holds the largest air")
}
#[test]
fn the_carve_never_has_more_than_two_sizes() {
let uneven = [gb(14.5), gb(12.0), gb(9.1), gb(7.0), gb(3.2)];
for budget in [20.0, 26.15, 33.0, 40.0, 60.0, 80.0, 200.0] {
for sizes in [&ZISK_BASIC_GB.iter().copied().map(gb).collect::<Vec<_>>()[..], &uneven[..]] {
let layout =
plan_stream_layout(gb(budget), sizes, gb(ZISK_COMPRESSOR_GB), gb(ZISK_RECURSIVE_GB), 16, 10);
let Some(layout) = layout else { continue };
assert!(layout.basic.len() <= 2, "budget {budget}: {:?}", layout.basic);
}
}
}
#[test]
fn a_roomy_budget_goes_uniform() {
let layout = zisk_layout(120.0, 4, 10);
assert_eq!(layout.basic, vec![StreamClass { size: gb(14.57), count: 4 }], "{:?}", layout.basic);
}
#[test]
fn a_class_that_no_longer_fits_yields_to_the_other() {
let layout = zisk_layout(70.0, 16, 10);
assert_eq!(
layout.basic,
vec![StreamClass { size: gb(14.57), count: 2 }, StreamClass { size: gb(6.45), count: 6 }]
);
}
#[test]
fn a_smaller_regular_is_only_taken_when_it_buys_a_stream() {
for budget in [ZISK_BUDGET_GB, 33.0, 40.0, 60.0, 80.0] {
let layout = zisk_layout(budget, 16, 10);
assert_eq!(layout.basic[1].size, gb(6.45), "budget {budget}: {:?}", layout.basic);
}
}
#[test]
fn basic_classes_are_funded_before_recursive_streams() {
let layout = zisk_layout(ZISK_BUDGET_GB, 16, 10);
assert_eq!(
layout.basic,
vec![StreamClass { size: gb(14.57), count: 1 }, StreamClass { size: gb(6.45), count: 1 }]
);
assert!(layout.basic[1].size >= gb(ZISK_COMPRESSOR_GB), "every basic class must hold a compressor");
assert!(layout.recursive.count >= 3, "aggregation still gets the remainder: {:?}", layout.recursive);
}
#[test]
fn preloaded_sizes_put_the_second_class_at_the_compressor_floor() {
let sizes = [gb(14.57), gb(5.9), gb(3.8), gb(2.6), gb(0.75)];
let layout =
plan_stream_layout(gb(26.15), &sizes, gb(ZISK_COMPRESSOR_GB), gb(ZISK_RECURSIVE_GB), 16, 10).unwrap();
assert_eq!(
layout.basic,
vec![StreamClass { size: gb(14.57), count: 1 }, StreamClass { size: gb(ZISK_COMPRESSOR_GB), count: 1 }]
);
assert_eq!(layout.recursive.count, 4);
}
#[test]
fn the_recursive_floor_does_not_cost_the_second_class() {
let sizes = [gb(14.57), gb(5.9), gb(3.8), gb(2.6), gb(0.75)];
let layout =
plan_stream_layout(gb(26.15), &sizes, gb(ZISK_COMPRESSOR_GB), gb(ZISK_RECURSIVE_GB), 16, 10).unwrap();
assert_eq!(layout.n_basic_streams(), 2, "second class lost to the floor: {:?}", layout.basic);
assert_eq!(layout.recursive.count, 4, "floor is a minimum, not a cap: {:?}", layout.recursive);
}
#[test]
fn a_large_budget_still_gets_recursive_streams() {
let sizes = [gb(14.5), gb(12.0), gb(6.23), gb(3.8), gb(1.5)];
for budget in [33.0, 40.0, 80.0] {
let layout = plan_stream_layout(gb(budget), &sizes, gb(6.24), gb(ZISK_RECURSIVE_GB), 16, 10).unwrap();
assert!(
layout.recursive.count >= RECURSIVE_STREAM_FLOOR,
"budget {budget}: {:?} / {:?}",
layout.basic,
layout.recursive
);
}
}
#[test]
fn the_recursive_cap_still_bounds_the_remainder() {
let layout = zisk_layout(ZISK_BUDGET_GB, 16, 1);
assert_eq!(layout.recursive.count, 1);
assert!(layout.unused > 0);
}
#[test]
fn a_larger_budget_cuts_more_classes() {
let layout = zisk_layout(80.0, 16, 8);
assert!(layout.n_basic_streams() >= 4, "got {:?}", layout.basic);
let capped = zisk_layout(80.0, 3, 8);
assert_eq!(capped.n_basic_streams(), 3);
assert_eq!(capped.recursive.count, 8);
}
#[test]
fn classes_never_fall_below_the_compressor_floor() {
let layout = zisk_layout(ZISK_BUDGET_GB, 16, 0);
for class in &layout.basic {
assert!(class.size >= gb(ZISK_COMPRESSOR_GB), "class {class:?} cannot host a compressor");
}
}
#[test]
fn a_no_aggregation_carve_still_hosts_the_airs() {
let sizes: Vec<usize> = ZISK_BASIC_GB.iter().copied().map(gb).collect();
let layout = plan_stream_layout(gb(29.05), &sizes, 0, 0, 20, 0).expect("budget holds the largest air");
assert!(layout.n_basic_streams() >= 2, "no second stream to overlap with: {:?}", layout.basic);
assert_eq!(layout.basic[1].size, gb(6.45), "second class cannot host the bulk airs: {:?}", layout.basic);
assert!(
layout.basic.iter().all(|c| c.size >= gb(6.45)),
"a class below the non-outlier floor survived: {:?}",
layout.basic
);
}
#[test]
fn coverage_never_costs_the_second_class() {
let sizes = [gb(14.5), gb(12.0), gb(3.2), gb(3.2), gb(3.2)];
let layout = plan_stream_layout(gb(26.0), &sizes, 0, 0, 16, 0).expect("budget holds the largest air");
assert!(layout.n_basic_streams() >= 2, "fallback did not engage: {:?}", layout.basic);
}
#[test]
fn the_basic_stream_cap_is_respected() {
let layout = zisk_layout(80.0, 2, 0);
assert_eq!(layout.n_basic_streams(), 2);
}
#[test]
fn leftover_is_smaller_than_any_further_class() {
let layout = zisk_layout(ZISK_BUDGET_GB, 16, 3);
assert!(layout.unused < gb(ZISK_COMPRESSOR_GB));
}
#[test]
fn carve_shape_with_a_second_near_outlier() {
let sizes = [gb(14.5), gb(12.0), gb(6.23), gb(3.8), gb(1.5)];
for budget in [20.0, 26.15, 33.0, 40.0, 80.0] {
let layout = plan_stream_layout(gb(budget), &sizes, gb(6.24), gb(ZISK_RECURSIVE_GB), 16, 10).unwrap();
let classes: Vec<String> = layout
.basic
.iter()
.map(|c| format!("{} x {:.2}", c.count, c.size as f64 * 8.0 / (1 << 30) as f64))
.collect();
println!(
"budget {budget:>6.2} -> basic [{}] recursive {} unused {:.2}",
classes.join(" + "),
layout.recursive.count,
layout.unused as f64 * 8.0 / (1 << 30) as f64,
);
}
}
#[test]
fn an_air_too_big_for_the_regular_class_falls_back_to_the_large_one() {
let sizes = [gb(14.5), gb(12.0), gb(6.23)];
let layout = plan_stream_layout(gb(26.15), &sizes, gb(6.24), gb(ZISK_RECURSIVE_GB), 16, 10).unwrap();
assert_eq!(
layout.basic,
vec![StreamClass { size: gb(14.5), count: 1 }, StreamClass { size: gb(6.24), count: 1 }]
);
assert!(layout.basic[1].size >= gb(6.23));
assert!(layout.recursive.count >= 3);
}
#[test]
fn a_split_that_buys_no_stream_loses_to_uniform() {
let sizes: Vec<usize> =
[7.42, 5.99, 5.82, 5.57, 5.31, 4.52, 4.43, 3.82, 2.60, 1.47, 0.75].iter().map(|&g| gb(g)).collect();
let layout = plan_stream_layout(gb(26.18), &sizes, gb(6.24), gb(1.33), 16, 10).unwrap();
assert_eq!(layout.basic, vec![StreamClass { size: gb(7.42), count: 3 }], "should be uniform");
assert!(sizes.iter().all(|&s| s <= layout.basic[0].size));
assert!(layout.recursive.count >= 1, "aggregation must keep at least the floor");
}
#[test]
fn a_real_outlier_still_gets_its_own_class() {
let layout = zisk_layout(70.0, 16, 10);
assert_eq!(layout.basic.len(), 2, "outlier must not be collapsed: {:?}", layout.basic);
assert_eq!(layout.basic[0].size, gb(14.57));
}
#[test]
fn too_small_a_budget_is_rejected() {
let sizes = [gb(14.57)];
assert!(plan_stream_layout(gb(10.0), &sizes, gb(6.24), gb(1.33), 16, 8).is_none());
}
#[test]
fn first_class_covers_both_the_largest_air_and_the_compressor() {
let layout = plan_stream_layout(gb(40.0), &[gb(2.0)], gb(9.0), gb(1.0), 16, 0).unwrap();
assert_eq!(layout.basic[0].size, gb(9.0));
let layout = plan_stream_layout(gb(40.0), &[gb(12.0)], gb(9.0), gb(1.0), 16, 0).unwrap();
assert_eq!(layout.basic[0].size, gb(12.0));
}
#[test]
fn stream_sizes_expand_classes_largest_first() {
let layout = StreamLayout {
basic: vec![StreamClass { size: 9, count: 1 }, StreamClass { size: 4, count: 2 }],
recursive: StreamClass { size: 1, count: 3 },
unused: 0,
};
assert_eq!(layout.basic_stream_sizes(), vec![9, 4, 4]);
assert_eq!(layout.n_basic_streams(), 3);
}
fn hosted(class: &StreamClass, sizes: &[usize]) -> usize {
sizes.iter().filter(|&&s| s <= class.size).count()
}
#[test]
fn the_classes_are_balanced_by_the_work_each_must_run() {
let sizes = [gb(14.5), gb(12.0), gb(6.23), gb(5.9), gb(5.4), gb(3.8), gb(2.6), gb(1.5)];
let layout = plan_stream_layout(gb(40.0), &sizes, 0, 0, 20, 0).unwrap();
assert_eq!(
layout.basic,
vec![StreamClass { size: gb(14.5), count: 2 }, StreamClass { size: gb(6.23), count: 1 }]
);
}
#[test]
fn aggregation_also_sizes_the_regular_class_at_the_bulk() {
let sizes = [gb(14.5), gb(12.0), gb(6.23), gb(5.9), gb(5.4), gb(3.8), gb(2.6), gb(1.5)];
let layout = plan_stream_layout(gb(29.05), &sizes, gb(6.24), gb(1.33), 20, 10).unwrap();
assert_eq!(
layout.basic,
vec![StreamClass { size: gb(14.5), count: 1 }, StreamClass { size: gb(6.24), count: 2 }]
);
}
#[test]
fn no_carve_buys_a_class_below_half_the_airs() {
let shapes: [&[f64]; 3] =
[&[14.5, 12.0, 6.23, 5.9, 5.4, 3.8, 2.6, 1.5], ZISK_BASIC_GB, &[14.57, 5.9, 3.8, 2.6, 0.75]];
for shape in shapes {
let sizes: Vec<usize> = shape.iter().copied().map(gb).collect();
for budget in [26.0, 26.15, 29.05, 33.0, 40.0, 60.0, 80.0] {
for (floor, rec, max_rec) in [(0.0, 0.0, 0), (ZISK_COMPRESSOR_GB, ZISK_RECURSIVE_GB, 10)] {
let layout = plan_stream_layout(gb(budget), &sizes, gb(floor), gb(rec), 20, max_rec).unwrap();
for class in &layout.basic {
assert!(
hosted(class, &sizes) * 2 >= sizes.len(),
"budget {budget} floor {floor}: class {class:?} hosts {} of {}: {:?}",
hosted(class, &sizes),
sizes.len(),
layout.basic
);
}
}
}
}
}
}