//! What a model batch costs on the keypress path, which is where the reshape //! observation sits (improvise-ni6k). Run with //! `cargo bench --bench reshape_observation`. use std::time::{Duration, 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::{CycleAxis, Effect, SetCell, ViewTarget}; /// The fixture `grid_projection` uses, so the two numbers are comparable. fn dense_app(regions: usize, products: usize, measures: usize) -> App { let mut wb = Workbook::new("Bench"); wb.add_category("Region").unwrap(); wb.add_category("Product").unwrap(); let regions: Vec = (0..regions).map(|i| format!("Region{i}")).collect(); let products: Vec = (0..products).map(|i| format!("Product{i}")).collect(); let measures: Vec = (0..measures).map(|i| format!("Measure{i}")).collect(); for r in ®ions { wb.add_item("Region", r.clone()).unwrap(); } for p in &products { wb.add_item("Product", p.clone()).unwrap(); } for m in &measures { wb.add_item("_Measure", m.clone()).unwrap(); } { let v = wb.active_view_mut(); v.set_axis("Region", Axis::Row); v.set_axis("Product", Axis::Row); v.set_axis("_Measure", Axis::Column); } let mut n = 0.0f64; for r in ®ions { for p in &products { for m in &measures { n += 1.0; let key = CellKey::new(vec![ ("Region".to_string(), r.clone()), ("Product".to_string(), p.clone()), ("_Measure".to_string(), m.clone()), ]); wb.model.set_cell(key, CellValue::Number(n)); } } } // More than one view, because the observation runs over all of them. wb.create_view("Second"); wb.switch_view("Default").unwrap(); let mut app = App::new(wb, None); app.rebuild_layout(); app } /// Min-of-N for the reason `grid_projection` says: a mean over a few runs /// measures the machine, not the code. fn time_batch(iters: u32, app: &mut App, key: &CellKey) -> Duration { let mut best = Duration::MAX; for i in 0..iters.max(3) { let effects: Vec> = vec![Box::new(SetCell( key.clone(), CellValue::Number(i as f64 + 1.0), ))]; let start = Instant::now(); std::hint::black_box(app.apply_effects(effects)); best = best.min(start.elapsed()); } best } fn case(label: &str, regions: usize, products: usize, measures: usize, iters: u32) { let mut app = dense_app(regions, products, measures); let (rows, cols) = (app.layout.row_count(), app.layout.col_count()); let key = CellKey::new(vec![ ("Region".to_string(), "Region0".to_string()), ("Product".to_string(), "Product0".to_string()), ("_Measure".to_string(), "Measure0".to_string()), ]); let batch = time_batch(iters, &mut app, &key); println!( "{label:<22} {rows:>5} x {cols:>3} = {:>7} cells one SetCell batch {batch:>11.3?}", rows * cols ); } /// The **held-key** path improvise-748f exists for: a geometry batch writes no /// cell, so `document_content` never runs and the frame fingerprint is alone. fn geometry_case(label: &str, regions: usize, products: usize, measures: usize, iters: u32) { // DO NOT set this false to "simplify". `prune_empty` is the ONLY thing that // makes the fingerprint walk cells — see `context/code/command.md`. geometry_at(label, regions, products, measures, iters, true); } fn geometry_at( label: &str, regions: usize, products: usize, measures: usize, iters: u32, prune: bool, ) { let mut app = dense_app(regions, products, measures); app.model_state.workbook.active_view_mut().prune_empty = prune; app.rebuild_layout(); let (rows, cols) = (app.layout.row_count(), app.layout.col_count()); let mut best = Duration::MAX; for i in 0..iters.max(3) { let effects: Vec> = vec![Box::new(CycleAxis( ViewTarget::Active, "Product".to_string(), ))]; let start = Instant::now(); std::hint::black_box(app.apply_effects(effects)); best = best.min(start.elapsed()); let _ = i; } println!( "{label:<22} {rows:>5} x {cols:>3} = {:>7} cells one CycleAxis batch {best:>11.3?}", rows * cols ); } fn main() { println!("App::apply_effects, one model batch = one keypress that writes a cell"); println!("a 60fps frame budget is 16.7ms\n"); case("typical", 20, 5, 4, 200); case("large", 60, 20, 8, 50); case("18k cells (gotcha 41)", 150, 20, 6, 20); case("pathological", 300, 30, 10, 5); println!("\nthe held-key path (improvise-748f): a batch that writes no cell"); geometry_case("typical", 20, 5, 4, 200); geometry_case("large", 60, 20, 8, 50); geometry_case("18k cells (gotcha 41)", 150, 20, 6, 20); geometry_case("pathological", 300, 30, 10, 5); }