//! Censo del reparto `View`/`ViewRare` contra el árbol de una app **real**, //! headless. //! //! Bloque 21, palanca 2b de `PLAN-NUCLEO-RETENIDO.md`. El ahorro del reparto //! depende de una sola cifra —la fracción de nodos que lleva alguna prop rara y //! paga la caja— y esa cifra la da el árbol de la app, no un benchmark: contra //! `stress_grid` daba −74 % y contra la galería real, −25,9 %. Toda la //! corrección del reparto salió de medir esto. //! //! No abre ventana ni toca la GPU: arma el Model con un `Handle` de tests, llama //! `App::view` una vez y recorre el árbol. Sale por stdout, es determinista, y //! cuesta lo que cuesta un `view()` — el modo barato de evidencia que pide la //! Regla 8 del repo. //! //! Enchufarlo en una app es una línea en su `main`: //! //! ```ignore //! if std::env::args().any(|a| a == "--census") { //! llimphi_ui::censo_app::(); //! return; //! } //! ``` use crate::{censo_rare, mascaras_rare, tam_rare, App, CensoRare, Handle, View, ViewRare}; /// Corre el censo de `A` e imprime el informe. Ver el módulo. pub fn censo_app() { // `init` sólo recibe el handle para despachar; acá los Msg se tiran porque // no hay bucle que los aplique. let modelo = A::init(&Handle::::for_test_with(|_| {})); let vista: View = A::view(&modelo); informe::(&vista, A::title()); } /// El informe sobre un árbol ya construido — separado de [`censo_app`] para que /// una app con un Model caro de armar pueda pasar el suyo. pub fn informe(vista: &View, nombre: &str) { let c = censo_rare(vista); let (antes, ahora) = (c.bytes_antes(), c.bytes_ahora::()); let base_view = core::mem::size_of::>(); let base_rare = core::mem::size_of::>(); println!("{nombre} — censo de props raras (árbol real, headless)"); println!(" nodos {}", c.nodos); println!( " con caja ViewRare {} ({:.1} %)", c.con_rare, 100.0 * c.fraccion() ); println!( " size_of View / Rare {base_view} B / {base_rare} B (antes del reparto: {} B por nodo)", CensoRare::VIEW_ANTES, ); println!( " bytes del árbol {antes} → {ahora} ({:+.1} %)", 100.0 * (ahora as f64 - antes as f64) / antes as f64 ); println!( " asignaciones extra {} (una caja por nodo que usa alguna prop rara)", c.con_rare ); if c.con_rare > 0 { println!(" qué prop hace rara a cada caja:"); for (campo, n) in crate::censo_campos(vista) { println!(" {n:>5} {campo}"); } } // ¿Convendría devolver alguna prop a `View`? La marginal por campo NO // alcanza: lo que importa es cuántos nodos quedarían SIN ninguna prop rara // (ésos dejan de pagar la caja entera), y eso depende del conjunto por nodo. // Con la máscara se evalúa exacto; se elige por búsqueda voraz. let mascaras = mascaras_rare(vista); let tam = tam_rare::(); let bytes_con = |s: u64| -> usize { let crecio: usize = (0..tam.len()).filter(|k| s & (1 << k) != 0).map(|k| tam[k]).sum(); let cajas = mascaras.iter().filter(|m| *m & !s != 0).count(); c.nodos * (base_view + crecio) + cajas * (base_rare - crecio) }; let mut s = 0u64; let mut mejor = bytes_con(0); loop { let cand = (0..tam.len()) .filter(|k| s & (1 << k) == 0) .map(|k| (bytes_con(s | (1 << k)), k)) .min(); match cand { Some((b, k)) if b < mejor => { mejor = b; s |= 1 << k; } _ => break, } } if s == 0 { println!(" reparto óptimo para este árbol: el vigente (no conviene mover ningún campo)"); return; } let elegidas: Vec<&str> = (0..tam.len()) .filter(|k| s & (1 << k) != 0) .map(|k| crate::NOMBRES_RARE[k]) .collect(); let crecio: usize = (0..tam.len()).filter(|k| s & (1 << k) != 0).map(|k| tam[k]).sum(); let cajas = mascaras.iter().filter(|m| *m & !s != 0).count(); println!(" este árbol preferiría OTRO reparto (búsqueda voraz sobre la máscara):"); println!(" volver inline {elegidas:?}"); println!(" View {base_view} B → {} B", base_view + crecio); println!(" cajas {} → {cajas}", c.con_rare); println!( " bytes del árbol {ahora} → {mejor} ({:+.1} % contra el View viejo)", 100.0 * (mejor as f64 - antes as f64) / antes as f64 ); println!( " contracara: un árbol uniforme SIN props raras (tipo stress_grid)\n\ \x20 pasaría de {base_view} a {} B por nodo, {:+.1} %", base_view + crecio, 100.0 * crecio as f64 / base_view as f64 ); }