use std::path::PathBuf;
use std::process::Command;
use std::time::Instant;
use ldeflate::{Config, compress_objects, compress_objects_serial};
fn git_objects(repo: &str, cap_objects: usize) -> Vec<Vec<u8>> {
let Ok(out) = Command::new("git")
.args([
"-C",
repo,
"cat-file",
"--batch-all-objects",
"--batch",
"--buffer",
])
.output()
else {
return Vec::new();
};
if !out.status.success() {
return Vec::new();
}
let buf = out.stdout;
let mut objs = Vec::new();
let mut i = 0usize;
while i < buf.len() && objs.len() < cap_objects {
let Some(nl) = buf[i..].iter().position(|&b| b == b'\n') else {
break;
};
let Ok(h) = std::str::from_utf8(&buf[i..i + nl]) else {
break;
};
i += nl + 1;
let size = h
.split_whitespace()
.nth(2)
.and_then(|s| s.parse::<usize>().ok());
let Some(size) = size else { break };
if i + size > buf.len() {
break;
}
objs.push(buf[i..i + size].to_vec());
i += size + 1;
}
objs
}
const REPOS: &[&str] = &[
"nornir",
"dwarves",
"korp",
"znippy",
"skade",
"edda",
"holger",
"znippy-zoomies",
];
fn repo_dir(name: &str) -> String {
format!("{}/git/{name}", std::env::var("HOME").unwrap_or_default())
}
fn git_corpus() -> Vec<Vec<u8>> {
let mut all = Vec::new();
for name in REPOS.iter().copied() {
let r = repo_dir(name);
let r = r.as_str();
if !PathBuf::from(r).join(".git").exists() {
continue;
}
let n0 = all.len();
all.extend(git_objects(r, 200_000));
eprintln!(" corpus: {r} -> {} objects", all.len() - n0);
}
all
}
fn large_corpus(total_mb: usize, compressible: bool) -> Vec<Vec<u8>> {
let each = 64usize << 20;
let n = ((total_mb << 20) / each).max(1);
(0..n)
.map(|k| {
let mut v = Vec::with_capacity(each);
let mut s: u64 = 0x243F6A8885A308D3 ^ (k as u64).wrapping_mul(0x9E3779B97F4A7C15);
let words: [&[u8]; 4] = [
b"the quick brown fox jumps over the lazy dog\n",
b"pub fn run() -> usize { let x = 1; x + 2 }\n",
b"2026-08-03T10:15:00Z INFO request id=af39c0 status=200 dur_ms=42\n",
b"use std::collections::HashMap;\n",
];
while v.len() < each {
s ^= s << 13;
s ^= s >> 7;
s ^= s << 17;
if compressible {
v.extend_from_slice(words[(s >> 33) as usize % words.len()]);
} else {
v.extend_from_slice(&s.to_le_bytes());
}
}
v.truncate(each);
v
})
.collect()
}
fn uniform_corpus(n: usize, size: usize) -> Vec<Vec<u8>> {
(0..n)
.map(|i| {
let mut v = Vec::with_capacity(size);
let tag = format!("uniform-{i}-");
while v.len() < size {
v.extend_from_slice(tag.as_bytes());
v.extend_from_slice(b"pub fn run() -> usize { 1 }\n");
}
v.truncate(size);
v
})
.collect()
}
fn tiny(sz: usize) -> Vec<u8> {
let mut v = Vec::with_capacity(sz);
let words: [&[u8]; 2] = [b"pub fn run() -> usize { 1 }\n", b"use std::io::Write;\n"];
let mut k = 0usize;
while v.len() < sz {
v.extend_from_slice(words[k % words.len()]);
k += 1;
}
v.truncate(sz);
v
}
fn cores() -> usize {
std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(1)
}
fn loadavg() -> f64 {
std::fs::read_to_string("/proc/loadavg")
.ok()
.and_then(|s| s.split_whitespace().next().and_then(|x| x.parse().ok()))
.unwrap_or(f64::NAN)
}
fn proc_cpu_seconds() -> f64 {
let txt = std::fs::read_to_string("/proc/self/stat").unwrap_or_default();
let Some(r) = txt.rfind(')') else { return 0.0 };
let f: Vec<&str> = txt[r + 1..].split_whitespace().collect();
let u: f64 = f.get(11).and_then(|s| s.parse().ok()).unwrap_or(0.0);
let s: f64 = f.get(12).and_then(|s| s.parse().ok()).unwrap_or(0.0);
(u + s) / 100.0
}
fn cpu_snapshot() -> Vec<(u64, u64)> {
let txt = std::fs::read_to_string("/proc/stat").unwrap_or_default();
txt.lines()
.filter(|l| l.starts_with("cpu") && l.as_bytes().get(3).is_some_and(|b| b.is_ascii_digit()))
.map(|l| {
let v: Vec<u64> = l
.split_whitespace()
.skip(1)
.filter_map(|x| x.parse().ok())
.collect();
let total: u64 = v.iter().sum();
let idle = v.get(3).copied().unwrap_or(0) + v.get(4).copied().unwrap_or(0);
(total.saturating_sub(idle), total)
})
.collect()
}
fn cpu_delta(a: &[(u64, u64)], b: &[(u64, u64)]) -> Vec<f64> {
a.iter()
.zip(b)
.map(|(x, y)| {
let dt = y.1.saturating_sub(x.1);
if dt == 0 {
0.0
} else {
y.0.saturating_sub(x.0) as f64 / dt as f64
}
})
.collect()
}
struct Timed {
wall: f64,
cpu: f64,
out_bytes: usize,
}
impl Timed {
fn new() -> Self {
Timed {
wall: f64::INFINITY,
cpu: f64::INFINITY,
out_bytes: 0,
}
}
fn sample(&mut self, f: &mut impl FnMut() -> usize) {
let c0 = proc_cpu_seconds();
let t0 = Instant::now();
let b = f();
let w = t0.elapsed().as_secs_f64();
let c = proc_cpu_seconds() - c0;
if w < self.wall {
self.wall = w;
}
if c < self.cpu {
self.cpu = c;
}
self.out_bytes = b;
}
fn cpu_s_per_gib(&self, in_bytes: usize) -> f64 {
self.cpu / (in_bytes as f64 / (1024.0 * 1024.0 * 1024.0))
}
}
fn time_run(iters: usize, mut f: impl FnMut() -> usize) -> Timed {
let mut t = Timed::new();
for _ in 0..iters {
t.sample(&mut f);
}
t
}
fn mbs(bytes: usize, secs: f64) -> f64 {
(bytes as f64 / (1024.0 * 1024.0)) / secs
}
fn run_cfg<'a>(refs: &'a [&'a [u8]], cfg: &'a Config) -> impl FnMut() -> usize + 'a {
move || {
compress_objects(refs, cfg)
.unwrap()
.iter()
.map(|v| v.len())
.sum()
}
}
fn arm_corpus(objs: &[Vec<u8>], label: &str) {
let mut sizes: Vec<usize> = objs.iter().map(|o| o.len()).collect();
sizes.sort_unstable();
let n = sizes.len();
if n == 0 {
println!("{label}: EMPTY");
return;
}
let total: usize = sizes.iter().sum();
let pct = |p: f64| sizes[((n as f64 - 1.0) * p) as usize];
println!(
"\n-- corpus: {label} --\n\
objects {n}\n\
total bytes {total} ({:.1} MiB)\n\
mean {} B\n\
p10 / p50 {} / {} B\n\
p90 / p99 {} / {} B\n\
p99.9 / max {} / {} B\n\
objects under 1 KiB {:.1}% | 4 KiB {:.1}% | 64 KiB {:.1}%\n\
BYTES in objects < 64 KiB: {:.1}%",
total as f64 / (1024.0 * 1024.0),
total / n,
pct(0.10),
pct(0.50),
pct(0.90),
pct(0.99),
pct(0.999),
sizes[n - 1],
100.0 * sizes.iter().filter(|&&s| s < 1024).count() as f64 / n as f64,
100.0 * sizes.iter().filter(|&&s| s < 4096).count() as f64 / n as f64,
100.0 * sizes.iter().filter(|&&s| s < 65536).count() as f64 / n as f64,
100.0
* sizes
.iter()
.filter(|&&s| s < 65536)
.map(|&s| s as f64)
.sum::<f64>()
/ total as f64,
);
}
fn arm_dispatch() {
println!("\n-- dispatch cost vs work cost (models: repack / loose-object write) --");
let w = cores();
for &n in &[10_000usize, 100_000, 1_000_000] {
let units: Vec<usize> = (0..n).collect();
for &claim in &[1usize, 4, 16] {
let t = time_run(5, || {
let out = gatling::gatling_forkjoin::gatling_map_balanced(
&units,
w,
claim,
|_| 1u64,
|_, &i| i,
);
std::hint::black_box(&out);
n
});
println!(
" {n:>9} EMPTY units, claim {claim:>2} -> {:>7.1} ns/unit",
t.wall * 1e9 / n as f64
);
}
}
println!("\n serial cost of ONE deflate at level 6, window 15 (one core, min-of-9):");
println!(" {:>10} {:>12} {:>14}", "size", "ns/object", "MB/s");
for &sz in &[
64usize, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 65536, 262144,
] {
let src = tiny(sz);
let mut enc = ldeflate::Encoder::new(6, true).unwrap();
let mut out = Vec::new();
let t = time_run(9, || {
for _ in 0..1000 {
out.clear();
enc.compress_into(&src, &mut out).unwrap();
}
sz * 1000
});
println!(
" {:>10} {:>12.0} {:>14.1}",
sz,
t.wall * 1e9 / 1000.0,
mbs(sz * 1000, t.wall)
);
}
}
fn arm_window(objs: &[Vec<u8>]) {
println!("\n-- window-bits sweep: where the per-object floor comes from --");
println!(" (models: loose-object writes, repack of small objects)\n");
println!(" cost of ONE deflate at level 6, by window (one core, min-of-9), ns/object:");
print!(" {:>10}", "size");
for wb in 9..=15u8 {
print!(" {:>8}", format!("wb{wb}"));
}
println!();
for &sz in &[64usize, 256, 1024, 4096, 16384] {
let src = tiny(sz);
print!(" {:>10}", sz);
for wb in 9..=15u8 {
let mut enc = ldeflate::Encoder::new_with_window(6, true, wb).unwrap();
let mut out = Vec::new();
let t = time_run(9, || {
for _ in 0..1000 {
out.clear();
enc.compress_into(&src, &mut out).unwrap();
}
sz * 1000
});
print!(" {:>8.0}", t.wall * 1e9 / 1000.0);
}
println!();
}
if objs.is_empty() {
return;
}
let refs: Vec<&[u8]> = objs.iter().map(|o| o.as_slice()).collect();
let total: usize = objs.iter().map(|o| o.len()).sum();
println!(
"\n end-to-end on {} REAL git objects ({:.1} MiB), {} workers, min-of-7 round-robin:",
objs.len(),
total as f64 / (1 << 20) as f64,
cores()
);
println!(
" {:>6} {:>13} {:>9} {:>10} {:>7} {:>12}",
"window", "cpu-s per GiB", "vs wb15", "wall MB/s", "ratio", "size vs wb15"
);
let wbs: Vec<u8> = (9..=15).collect();
let cfgs: Vec<Config> = wbs
.iter()
.map(|&wb| Config {
window_bits: wb,
..Config::default()
})
.collect();
let mut acc: Vec<Timed> = wbs.iter().map(|_| Timed::new()).collect();
for _ in 0..7 {
for (i, cfg) in cfgs.iter().enumerate() {
acc[i].sample(&mut run_cfg(&refs, cfg));
}
}
let base_cpu = acc[wbs.len() - 1].cpu;
let base_sz = acc[wbs.len() - 1].out_bytes as f64;
for (i, &wb) in wbs.iter().enumerate() {
println!(
" {:>6} {:>13.2} {:>8.2}x {:>10.1} {:>7.3} {:>12.4}",
wb,
acc[i].cpu_s_per_gib(total),
base_cpu / acc[i].cpu,
mbs(total, acc[i].wall),
total as f64 / acc[i].out_bytes as f64,
acc[i].out_bytes as f64 / base_sz
);
}
}
fn arm_threshold(objs: &[Vec<u8>], label: &str) {
const REPS: usize = 7;
if objs.is_empty() {
return;
}
let refs: Vec<&[u8]> = objs.iter().map(|o| o.as_slice()).collect();
let total: usize = objs.iter().map(|o| o.len()).sum();
let sizes: Vec<usize> = objs.iter().map(|o| o.len()).collect();
let mut names: Vec<String> = Vec::new();
let mut cfgs: Vec<Config> = Vec::new();
for &cb in &[0usize, 256, 1024, 4096, 16 << 10, 64 << 10, 256 << 10] {
for &claim in &[1usize, 4, 16] {
names.push(format!(
"{}/{}",
if cb == 0 {
"off".into()
} else {
format!("{cb}")
},
claim
));
cfgs.push(Config {
coalesce_below: cb,
claim,
..Config::default()
});
}
}
let mut acc: Vec<Timed> = cfgs.iter().map(|_| Timed::new()).collect();
let l0 = loadavg();
for rep in 0..REPS {
for (i, cfg) in cfgs.iter().enumerate() {
acc[i].sample(&mut run_cfg(&refs, cfg));
}
eprint!(
" threshold rep {}/{REPS} (load {:.0}) \r",
rep + 1,
loadavg()
);
}
let l1 = loadavg();
println!(
"\n-- threshold sweep: {label} --\n\
{} objects, {:.1} MiB, {} workers, level 6, min-of-{REPS} ROUND-ROBIN\n\
box load during the sweep: {l0:.0} -> {l1:.0} on {} cores (SHARED BOX)\n",
objs.len(),
total as f64 / (1 << 20) as f64,
cores(),
cores()
);
println!(
"{:>16} {:>8} {:>13} {:>10} {:>10} {:>7}",
"coalesce/claim", "units", "cpu-s per GiB", "vs off/1", "wall MB/s", "ratio"
);
let base = acc[0].cpu;
for (i, name) in names.iter().enumerate() {
println!(
"{:>16} {:>8} {:>13.2} {:>9.2}x {:>10.1} {:>7.3}",
name,
ldeflate::plan(&sizes, &cfgs[i]).unwrap().len(),
acc[i].cpu_s_per_gib(total),
base / acc[i].cpu,
mbs(total, acc[i].wall),
total as f64 / acc[i].out_bytes as f64,
);
}
}
fn arm_scaling(objs: &[Vec<u8>], cfg: &Config, label: &str) {
const REPS: usize = 5;
if objs.is_empty() {
return;
}
let refs: Vec<&[u8]> = objs.iter().map(|o| o.as_slice()).collect();
let total: usize = objs.iter().map(|o| o.len()).sum();
let l0 = loadavg();
let widths: Vec<usize> = vec![1, 2, 4, 8, 16, 24, 32];
let cfgs: Vec<Config> = widths
.iter()
.map(|&w| Config { workers: w, ..*cfg })
.collect();
let mut acc: Vec<Timed> = cfgs.iter().map(|_| Timed::new()).collect();
let mut ser = Timed::new();
for _ in 0..REPS {
ser.sample(&mut || {
compress_objects_serial(&refs, cfg)
.unwrap()
.iter()
.map(|v| v.len())
.sum()
});
for (i, c) in cfgs.iter().enumerate() {
acc[i].sample(&mut run_cfg(&refs, c));
}
}
let l1 = loadavg();
let ser_mbs = mbs(total, ser.wall);
println!(
"\n-- scaling: {label} --\n\
{:.1} MiB in, level {}, window {}, min-of-{REPS} round-robin, load {l0:.0} -> {l1:.0}\n",
total as f64 / (1 << 20) as f64,
cfg.level,
cfg.window_bits
);
println!(
"{:>8} {:>10} {:>9} {:>6} {:>11} {:>7}",
"threads", "MB/s", "speedup", "eff", "cores busy", "ratio"
);
println!(
"{:>8} {:>10.1} {:>9} {:>6} {:>11.2} {:>7.3}",
"serial",
ser_mbs,
"1.00x",
"100%",
ser.cpu / ser.wall,
total as f64 / ser.out_bytes as f64
);
for (i, &w) in widths.iter().enumerate() {
let m = mbs(total, acc[i].wall);
println!(
"{:>8} {:>10.1} {:>8.2}x {:>5.0}% {:>11.2} {:>7.3}",
w,
m,
m / ser_mbs,
100.0 * (m / ser_mbs) / w as f64,
acc[i].cpu / acc[i].wall,
total as f64 / acc[i].out_bytes as f64
);
}
}
fn arm_sustained(objs: &[Vec<u8>], cfg: &Config, seconds: f64, label: &str) {
if objs.is_empty() {
return;
}
let refs: Vec<&[u8]> = objs.iter().map(|o| o.as_slice()).collect();
let total: usize = objs.iter().map(|o| o.len()).sum();
let ncpu = cores();
println!(
"\n-- sustained: {label} (target {seconds:.0}s, {ncpu} cores, load {:.0}) --",
loadavg()
);
let stop = std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false));
let stop2 = stop.clone();
let sampler = gatling::background::Job::spawn(move || {
let mut samples: Vec<Vec<f64>> = Vec::new();
let mut prev = cpu_snapshot();
while !stop2.load(std::sync::atomic::Ordering::Relaxed) {
std::thread::sleep(std::time::Duration::from_millis(100));
let now = cpu_snapshot();
samples.push(cpu_delta(&prev, &now));
prev = now;
}
samples
});
let c0 = proc_cpu_seconds();
let t0 = Instant::now();
let mut passes = 0usize;
let mut bytes = 0usize;
while t0.elapsed().as_secs_f64() < seconds {
let out = compress_objects(&refs, cfg).unwrap();
bytes += total;
std::hint::black_box(&out);
passes += 1;
}
let wall = t0.elapsed().as_secs_f64();
let our_cores = (proc_cpu_seconds() - c0) / wall;
stop.store(true, std::sync::atomic::Ordering::Relaxed);
let samples = sampler.join().unwrap();
let s: Vec<&Vec<f64>> = samples
.iter()
.skip(1)
.take(samples.len().saturating_sub(2))
.collect();
if s.is_empty() {
println!(" (no samples)");
return;
}
let busy: Vec<f64> = s
.iter()
.map(|w| w.iter().filter(|&&u| u > 0.5).count() as f64)
.collect();
let summed: Vec<f64> = s.iter().map(|w| w.iter().sum()).collect();
let mean = |v: &[f64]| v.iter().sum::<f64>() / v.len() as f64;
let mut sorted = summed.clone();
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
let p5 = sorted[(sorted.len() as f64 * 0.05) as usize];
let p50 = sorted[sorted.len() / 2];
let ncols = s[0].len();
let per_core: Vec<f64> = (0..ncols)
.map(|c| mean(&s.iter().map(|w| w[c]).collect::<Vec<_>>()))
.collect();
let min_core = per_core.iter().cloned().fold(f64::INFINITY, f64::min);
let max_core = per_core.iter().cloned().fold(0.0, f64::max);
println!(
" {passes} passes over {wall:.1}s -> {:.1} MB/s sustained\n\
\x20 cores >50% busy: mean {:.1} / {ncpu}, worst window {:.0}, best window {:.0}\n\
\x20 summed utilisation: p50 {:.1} / {ncpu} cores, p5 {:.1} (the dip floor)\n\
\x20 per-core mean over the run: min {:.0}%, max {:.0}% (spread {:.0} pp)\n\
\x20 {} sample windows of 100 ms\n\
\x20 OUR PROCESS held {:.2} of {ncpu} cores (cpu-seconds / wall) --- the /proc/stat\n\
\x20 figures above are WHOLE-BOX and include other tenants of this shared machine.\n\
\x20 Read the per-core spread as \"no core sat out\"; read OUR cores for our share.",
mbs(bytes, wall),
mean(&busy),
busy.iter().cloned().fold(f64::INFINITY, f64::min),
busy.iter().cloned().fold(0.0, f64::max),
p50,
p5,
min_core * 100.0,
max_core * 100.0,
(max_core - min_core) * 100.0,
s.len(),
our_cores,
);
}
fn arm_large() {
println!("\n=== LARGE / LFS-shaped arm (models: cold tier, LFS ingest) ===");
for &(compressible, label) in &[
(true, "compressible (text-like)"),
(false, "INCOMPRESSIBLE (video/archive-shaped)"),
] {
let objs = large_corpus(256, compressible);
arm_corpus(&objs, label);
let total: usize = objs.iter().map(|o| o.len()).sum();
let refs: Vec<&[u8]> = objs.iter().map(|o| o.as_slice()).collect();
println!(
"\n segment sweep ({label}, {} workers, min-of-3):",
cores()
);
println!(
" {:>10} {:>10} {:>7} {:>12}",
"segment", "MB/s", "ratio", "size vs none"
);
let unsplit_cfg = Config::default().without_splitting();
let t0 = time_run(3, run_cfg(&refs, &unsplit_cfg));
println!(
" {:>10} {:>10.1} {:>7.3} {:>12}",
"none",
mbs(total, t0.wall),
total as f64 / t0.out_bytes as f64,
"1.0000"
);
let unsplit = t0.out_bytes;
for &seg in &[64usize << 10, 256 << 10, 1 << 20, 4 << 20, 16 << 20] {
let cfg = Config {
split_above: 1 << 20,
segment: seg,
..Config::default()
};
let t = time_run(3, run_cfg(&refs, &cfg));
println!(
" {:>10} {:>10.1} {:>7.3} {:>12.4}",
seg,
mbs(total, t.wall),
total as f64 / t.out_bytes as f64,
t.out_bytes as f64 / unsplit as f64
);
}
arm_scaling(
&objs,
&Config {
split_above: 1 << 20,
segment: 1 << 20,
..Config::default()
},
&format!("SPLIT path, {label}"),
);
let one = &objs[0];
let t_def = time_run(3, || ldeflate::zlib_compress(one, 6).unwrap().len());
let t_sha = time_run(3, || {
use sha1::{Digest, Sha1};
let mut h = Sha1::new();
h.update(one);
std::hint::black_box(h.finalize());
one.len()
});
println!(
"\n ONE 64 MiB object on ONE core: deflate L6 {:.1} MB/s | SHA-1 {:.1} MB/s\n\
\x20 -> the hash runs {:.1}x the deflate rate, so on ALREADY-COMPRESSED content\n\
\x20 (most LFS content) the deflate is pure waste and the hash is the job.",
mbs(one.len(), t_def.wall),
mbs(one.len(), t_sha.wall),
(one.len() as f64 / t_sha.wall) / (one.len() as f64 / t_def.wall)
);
}
}
fn arm_uniform_control() {
println!("\n-- CORPUS CONTROL: uniform 1 MiB objects (the WRONG corpus) --");
println!(" Shown so it is on the record that this shape is not git-like.\n");
let objs = uniform_corpus(256, 1 << 20);
arm_corpus(&objs, "uniform 1 MiB x 256 (SYNTHETIC, not git-shaped)");
arm_scaling(
&objs,
&Config::default(),
"uniform 1 MiB objects (SYNTHETIC)",
);
}
fn pack_corpus(cap_bytes: usize) -> Vec<Vec<u8>> {
let mut out = Vec::new();
let mut total = 0usize;
for name in REPOS.iter().copied() {
let dir = PathBuf::from(repo_dir(name)).join(".git/objects/pack");
let Ok(rd) = std::fs::read_dir(&dir) else {
continue;
};
let mut names: Vec<_> = rd.filter_map(|e| e.ok()).map(|e| e.path()).collect();
names.sort();
for path in names {
let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
if !matches!(ext, "pack" | "idx") {
continue;
}
let Ok(bytes) = std::fs::read(&path) else {
continue;
};
if bytes.is_empty() {
continue;
}
total += bytes.len();
out.push(bytes);
if total >= cap_bytes {
return out;
}
}
}
out
}
fn arm_precompressed(objs: &[Vec<u8>], label: &str) {
if objs.is_empty() {
println!("\n-- precompressed: {label} -- (no corpus, skipped)");
return;
}
let refs: Vec<&[u8]> = objs.iter().map(|o| o.as_slice()).collect();
let total: usize = refs.iter().map(|o| o.len()).sum();
println!(
"\n-- precompressed: {label} --\n{:.1} MiB in, {} objects, level 6, load {:.0}",
total as f64 / 1048576.0,
refs.len(),
loadavg()
);
let on = Config::default();
let off = Config::default().compress_everything();
let t_off = time_run(3, || {
compress_objects(&refs, &off)
.unwrap()
.iter()
.map(|o| o.len())
.sum()
});
let t_on = time_run(3, || {
compress_objects(&refs, &on)
.unwrap()
.iter()
.map(|o| o.len())
.sum()
});
println!(
" {:>14} {:>14} {:>12} {:>8}",
"probe", "cpu-s per GiB", "wall MB/s", "ratio"
);
for (name, t) in [("off (deflate)", &t_off), ("on (store)", &t_on)] {
println!(
" {name:>14} {:>14.2} {:>12.1} {:>8.4}",
t.cpu_s_per_gib(total),
total as f64 / t.wall / 1048576.0,
total as f64 / t.out_bytes as f64
);
}
println!(
" -> {:.1}x less CPU ({:.1}% saved), size {:+.2}%",
t_off.cpu / t_on.cpu.max(f64::MIN_POSITIVE),
100.0 * (1.0 - t_on.cpu / t_off.cpu),
100.0 * (t_on.out_bytes as f64 / t_off.out_bytes as f64 - 1.0)
);
}
fn main() {
let arm = std::env::args().nth(1).unwrap_or_else(|| "all".into());
println!(
"ldeflate measure -- {} cores, {}, load {:.0}",
cores(),
std::fs::read_to_string("/proc/cpuinfo")
.ok()
.and_then(|s| s
.lines()
.find(|l| l.starts_with("model name"))
.and_then(|l| l.split(':').nth(1))
.map(|m| m.trim().to_string()))
.unwrap_or_else(|| "unknown".into()),
loadavg()
);
let need_git = matches!(
arm.as_str(),
"all" | "corpus" | "threshold" | "scaling" | "sustained" | "window"
);
let git = if need_git {
eprintln!("building the git-object corpus ...");
let c = git_corpus();
eprintln!("git corpus: {} objects", c.len());
c
} else {
Vec::new()
};
let small: Vec<Vec<u8>> = git.iter().filter(|o| o.len() < 65536).cloned().collect();
match arm.as_str() {
"corpus" => {
arm_corpus(&git, "REAL git objects");
arm_corpus(&small, "REAL git objects < 64 KiB");
}
"dispatch" => arm_dispatch(),
"window" => arm_window(&small),
"threshold" => {
arm_corpus(&git, "REAL git objects");
arm_threshold(&git, "REAL git objects, whole corpus");
arm_corpus(
&small,
"REAL git objects < 64 KiB (the dispatch-bound majority)",
);
arm_threshold(&small, "REAL git objects < 64 KiB");
}
"scaling" => arm_scaling(&git, &Config::default(), "git objects, tuned config"),
"sustained" => arm_sustained(&git, &Config::default(), 30.0, "git objects, tuned config"),
"large" => arm_large(),
"precompressed" => {
arm_precompressed(&pack_corpus(512 << 20), "REAL git packfiles + indexes")
}
"uniform" => arm_uniform_control(),
_ => {
arm_corpus(&git, "REAL git objects");
arm_dispatch();
arm_window(&small);
arm_threshold(&git, "REAL git objects, whole corpus");
arm_corpus(
&small,
"REAL git objects < 64 KiB (the dispatch-bound majority)",
);
arm_threshold(&small, "REAL git objects < 64 KiB");
arm_scaling(
&git,
&Config::default().without_coalescing(),
"git objects, NO coalescing (one unit per object)",
);
arm_scaling(&git, &Config::default(), "git objects, tuned config");
arm_sustained(&git, &Config::default(), 30.0, "git objects, tuned config");
arm_uniform_control();
arm_large();
}
}
}