13918bb924
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
267 lines
11 KiB
Rust
267 lines
11 KiB
Rust
//! One configuration per process: build a sparse document, time the real paths.
|
|
//! `scale_bisect k=1000 n=100000 cats=3 prune=1 mode=grid`; see context/test-evidence/scale-bisect.md.
|
|
|
|
use std::collections::{HashMap, HashSet};
|
|
use std::io::Write;
|
|
use std::time::Instant;
|
|
|
|
use improvise::model::cell::{CellKey, CellValue};
|
|
use improvise::view::Axis;
|
|
use improvise::workbook::Workbook;
|
|
use improvise_command::ui::app::App;
|
|
use improvise_command::ui::effect::{self, ViewTarget};
|
|
use improvise_command::ui::host::Host;
|
|
use improvise_command::ui::project::{self, Viewport};
|
|
use improvise_command::ui::reduce::caught_up;
|
|
use improvise_io::persistence::{format_md, parse_md, save_to};
|
|
use improvise_io::storage::MemoryFs;
|
|
use improvise_protocol::{
|
|
ClientFrame, KeyCode, KeyEvent, KeyModifiers, ModelCommand, RecordHash, render::RecordCell,
|
|
};
|
|
|
|
fn lcg(s: &mut u64, n: usize) -> usize {
|
|
*s = s
|
|
.wrapping_mul(6_364_136_223_846_793_005)
|
|
.wrapping_add(1_442_695_040_888_963_407);
|
|
(*s >> 33) as usize % n
|
|
}
|
|
|
|
struct Cfg {
|
|
k: usize,
|
|
n: usize,
|
|
cats: usize,
|
|
prune: bool,
|
|
mode: String,
|
|
groups: bool,
|
|
ops: String,
|
|
}
|
|
|
|
fn arg(args: &HashMap<String, String>, k: &str, d: &str) -> String {
|
|
args.get(k).cloned().unwrap_or_else(|| d.to_string())
|
|
}
|
|
|
|
fn document(c: &Cfg) -> (Workbook, Vec<CellKey>) {
|
|
let mut wb = Workbook::new("bench");
|
|
for ci in 0..c.cats {
|
|
let cat = format!("C{ci}");
|
|
wb.add_category(cat.clone()).expect("under the limit");
|
|
for i in 0..c.k {
|
|
if c.groups && ci == 0 {
|
|
wb.add_item_in_group(&cat, format!("i{i}"), format!("G{}", i / 10))
|
|
.expect("added");
|
|
} else {
|
|
wb.add_item(&cat, format!("i{i}")).expect("added");
|
|
}
|
|
}
|
|
}
|
|
for m in ["Cost", "Rev"] {
|
|
wb.add_item("_Measure", m).expect("virtual");
|
|
}
|
|
let space = (c.k as f64).powi(c.cats as i32);
|
|
let wanted = c.n.min((space / 2.0).max(1.0) as usize);
|
|
let (mut s, mut seen, mut recs) = (0x9E37_79B9_7F4A_7C15u64, HashSet::new(), Vec::new());
|
|
while seen.len() < wanted {
|
|
let d: Vec<usize> = (0..c.cats).map(|_| lcg(&mut s, c.k)).collect();
|
|
if !seen.insert(d.clone()) {
|
|
continue;
|
|
}
|
|
let coords: Vec<(String, String)> = d
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(ci, x)| (format!("C{ci}"), format!("i{x}")))
|
|
.collect();
|
|
recs.push(CellKey::new(coords.clone()));
|
|
for (m, v) in [("Cost", 3.0), ("Rev", 5.0)] {
|
|
let mut key = coords.clone();
|
|
key.push(("_Measure".to_string(), m.to_string()));
|
|
wb.model.set_cell(CellKey::new(key), CellValue::Number(v));
|
|
}
|
|
}
|
|
let view = wb.active_view_mut();
|
|
for ci in 0..c.cats {
|
|
let axis = if c.mode == "records" || c.mode == "none" { Axis::None } else { Axis::Row };
|
|
view.set_axis(&format!("C{ci}"), axis);
|
|
}
|
|
if c.mode == "records" {
|
|
view.set_axis("_Index", Axis::Row);
|
|
view.set_axis("_Dim", Axis::Column);
|
|
} else {
|
|
view.set_axis("_Measure", Axis::Column);
|
|
}
|
|
view.prune_empty = c.prune;
|
|
(wb, recs)
|
|
}
|
|
|
|
fn maxrss_mb() -> f64 {
|
|
let mut u: libc::rusage = unsafe { std::mem::zeroed() };
|
|
unsafe { libc::getrusage(libc::RUSAGE_SELF, &mut u) };
|
|
u.ru_maxrss as f64 / 1048576.0
|
|
}
|
|
|
|
fn rss_mb() -> f64 {
|
|
let out = std::process::Command::new("ps")
|
|
.args(["-o", "rss=", "-p", &std::process::id().to_string()])
|
|
.output()
|
|
.expect("ps");
|
|
String::from_utf8_lossy(&out.stdout).trim().parse::<f64>().unwrap_or(0.0) / 1024.0
|
|
}
|
|
|
|
/// Sample `f` up to `reps` times, stopping once `budget_ms` is spent (one sample minimum).
|
|
fn sample(name: &str, reps: usize, budget_ms: f64, mut f: impl FnMut(usize)) {
|
|
let mut v = Vec::new();
|
|
let start = Instant::now();
|
|
for r in 0..reps {
|
|
let t = Instant::now();
|
|
f(r);
|
|
v.push(t.elapsed().as_secs_f64() * 1000.0);
|
|
if start.elapsed().as_secs_f64() * 1000.0 > budget_ms {
|
|
break;
|
|
}
|
|
}
|
|
v.sort_by(|a, b| a.partial_cmp(b).expect("finite"));
|
|
println!(
|
|
"{name} min={:.3} med={:.3} max={:.3} reps={}",
|
|
v[0],
|
|
v[v.len() / 2],
|
|
v[v.len() - 1],
|
|
v.len()
|
|
);
|
|
std::io::stdout().flush().ok();
|
|
}
|
|
|
|
fn once<T>(name: &str, f: impl FnOnce() -> T) -> T {
|
|
let t = Instant::now();
|
|
let v = f();
|
|
println!("{name} min={0:.3} med={0:.3} max={0:.3} reps=1", t.elapsed().as_secs_f64() * 1000.0);
|
|
std::io::stdout().flush().ok();
|
|
v
|
|
}
|
|
|
|
fn key(code: KeyCode) -> KeyEvent {
|
|
KeyEvent::new(code, KeyModifiers::NONE)
|
|
}
|
|
|
|
fn main() {
|
|
let args: HashMap<String, String> = std::env::args()
|
|
.skip(1)
|
|
.filter_map(|a| a.split_once('=').map(|(k, v)| (k.to_string(), v.to_string())))
|
|
.collect();
|
|
let c = Cfg {
|
|
k: arg(&args, "k", "1000").parse().expect("k"),
|
|
n: arg(&args, "n", "100000").parse().expect("n"),
|
|
cats: arg(&args, "cats", "3").parse().expect("cats"),
|
|
prune: arg(&args, "prune", "1") == "1",
|
|
mode: arg(&args, "mode", "grid"),
|
|
groups: arg(&args, "groups", "0") == "1",
|
|
ops: arg(&args, "ops", "open,key,frame,window,setcell,host,reshape,io"),
|
|
};
|
|
let rows_cap: f64 = arg(&args, "rows_cap", "30000000").parse().expect("cap");
|
|
let product = (c.k as f64).powi(c.cats as i32);
|
|
if c.mode == "grid" && product > rows_cap {
|
|
println!("REFUSED product_rows={product:.3e} (> {rows_cap:.0}: layout would materialize it)");
|
|
return;
|
|
}
|
|
let want = |o: &str| c.ops.split(',').any(|x| x == o);
|
|
let (wb, recs) = document(&c);
|
|
println!("cells={} records={} base_rss_mb={:.0}", wb.model.data.iter_cells().count(), recs.len(), rss_mb());
|
|
let mut app = once("open", || App::new(wb, None));
|
|
println!("rows={} cols={} rss_after_open_mb={:.0}", app.layout.row_count(), app.layout.col_count(), rss_mb());
|
|
app.term_width = 200;
|
|
app.term_height = 50;
|
|
let name = app.view_name().to_string();
|
|
|
|
if want("key") {
|
|
sample("keypress_down", 8, 6000.0, |_| {
|
|
app.dispatch_key(key(KeyCode::Down));
|
|
});
|
|
}
|
|
if want("frame") {
|
|
let whole = |app: &App| Viewport { row_offset: 0, col_offset: 0, rows: app.layout.row_count(), cols: app.layout.col_count() };
|
|
sample("tui_frame_grid_of_whole", 3, 8000.0, |_| {
|
|
std::hint::black_box(project::grid_of(&app, whole(&app)));
|
|
});
|
|
let cache = project::grid_of(&app, whole(&app));
|
|
let area = ratatui::layout::Rect::new(0, 0, 200, 50);
|
|
sample("tui_render_gridwidget_buffer", 5, 4000.0, |_| {
|
|
let mut buf = ratatui::buffer::Buffer::empty(area);
|
|
let w = improvise::ui::grid::GridWidget::new(
|
|
&cache, app.cursor(), &app.view_state.active_view, &app.layout,
|
|
&app.view_state.mode, &app.view_state.search_query,
|
|
&app.view_state.buffers, app.drill_state.as_ref(),
|
|
);
|
|
ratatui::widgets::Widget::render(w, area, &mut buf);
|
|
std::hint::black_box(buf);
|
|
});
|
|
}
|
|
if want("window") {
|
|
let mid = app.layout.row_count() / 2;
|
|
for (label, off) in [("top", 0), ("mid", mid)] {
|
|
let vp = Viewport { row_offset: off, col_offset: 0, rows: 45, cols: 12 };
|
|
sample(&format!("browser_grid_of_{label}"), 5, 6000.0, |_| {
|
|
std::hint::black_box(project::grid_of(&app, vp));
|
|
});
|
|
}
|
|
}
|
|
if want("setcell") && !recs.is_empty() {
|
|
let existing = recs[recs.len() / 2].clone().with("_Measure", "Rev");
|
|
sample("setcell_existing", 5, 6000.0, |r| {
|
|
app.apply_effects(vec![Box::new(effect::SetCell(existing.clone(), CellValue::Number(10.0 + r as f64)))]);
|
|
});
|
|
let mut s = 99u64;
|
|
sample("setcell_new_record", 5, 6000.0, |_| {
|
|
let mut coords: Vec<(String, String)> = (0..c.cats).map(|ci| (format!("C{ci}"), format!("i{}", lcg(&mut s, c.k)))).collect();
|
|
coords.push(("_Measure".into(), "Rev".into()));
|
|
app.apply_effects(vec![Box::new(effect::SetCell(CellKey::new(coords), CellValue::Number(1.0)))]);
|
|
});
|
|
}
|
|
if want("reshape") && c.mode == "grid" {
|
|
let v = || ViewTarget::Active;
|
|
sample("reshape_hide_item", 3, 6000.0, |r| {
|
|
app.apply_effects(vec![Box::new(effect::HideItem { view: v(), category: "C1".into(), item: format!("i{r}") })]);
|
|
});
|
|
if c.groups {
|
|
sample("reshape_toggle_group", 3, 6000.0, |_| {
|
|
app.apply_effects(vec![Box::new(effect::ToggleGroup { view: v(), category: "C0".into(), group: "G0".into() })]);
|
|
});
|
|
}
|
|
let last = format!("C{}", c.cats - 1);
|
|
sample("reshape_set_axis", 4, 6000.0, |r| {
|
|
let axis = if r % 2 == 0 { Axis::Page } else { Axis::Row };
|
|
app.apply_effects(vec![Box::new(effect::SetAxis { view: v(), category: last.clone(), axis })]);
|
|
});
|
|
sample("reshape_toggle_prune", 2, 6000.0, |_| {
|
|
app.apply_effects(vec![Box::new(effect::TogglePruneEmpty(v()))]);
|
|
});
|
|
}
|
|
if want("io") {
|
|
let wbr = &app.model_state.workbook;
|
|
let text = once("format_md", || format_md(wbr));
|
|
println!("improv_mb={:.2}", text.len() as f64 / 1048576.0);
|
|
once("parse_md", || parse_md(&text).expect("parses"));
|
|
let fs = MemoryFs::new();
|
|
sample("persist_per_command_save_to", 3, 6000.0, |_| {
|
|
save_to(&fs, wbr, std::path::Path::new("d.improv")).expect("write");
|
|
});
|
|
}
|
|
if want("host") && !recs.is_empty() {
|
|
let mut host = Host::new(app);
|
|
let (id, snap) = once("host_connect_snapshot", || host.connect(45, 12));
|
|
sample("json_encode_snapshot", 3, 4000.0, |_| {
|
|
std::hint::black_box(serde_json::to_vec(&snap.msg).expect("json"));
|
|
});
|
|
println!("snapshot_kb={:.1}", serde_json::to_vec(&snap.msg).expect("json").len() as f64 / 1024.0);
|
|
let rec = recs[recs.len() / 2].clone();
|
|
let cell = RecordCell { record: RecordHash::of(&rec), measure: Some("Rev".into()) };
|
|
let mut enc: Vec<(f64, usize)> = Vec::new();
|
|
sample("host_handle_setcell_and_project", 4, 8000.0, |r| {
|
|
let wire = caught_up(host.app(), ModelCommand::SetCell { view: name.clone(), cell: cell.clone(), value: CellValue::Number(50.0 + r as f64) });
|
|
let out = host.handle(id, ClientFrame::command(wire));
|
|
let t = Instant::now();
|
|
let bytes: usize = out.iter().map(|a| serde_json::to_vec(&a.msg).expect("json").len()).sum();
|
|
enc.push((t.elapsed().as_secs_f64() * 1000.0, bytes));
|
|
});
|
|
println!("json_encode_setcell_updates ms={:?}", enc);
|
|
}
|
|
println!("maxrss_mb={:.0}", maxrss_mb());
|
|
}
|