perf(tahuantinsuyu): LRU cache de NatalChart por (birth, config, offset)
`NatalChart::compute` cuesta varios ms (VSOP2013 + casas + aspectos base). Bajo drag de slider en el panel, el shell dispara `compose()` decenas de veces — la natal del sujeto principal y la del partner de Synastry/Composite son **idénticas** entre frames pero hoy se recomputan. Nuevo `natal_cache.rs`: LRU de 8 entradas con `Mutex<Vec<(Key, Arc)>>`, key = hash de contenido `(StoredBirthData, StoredChartConfig, offset_minutes)`. Move-to-front en hit, evict del back cuando se llena. f64s se hashean vía `to_bits()`. `compute_natal_chart` ahora consulta el cache antes de delegar a eternal; firma cambia a devolver `Arc<NatalChart>` — los call sites (natal principal, partner de Synastry/Composite) usan auto-deref a través de `Arc::Deref` sin cambios. Editar una carta (cualquier campo de `StoredBirthData` o `StoredChartConfig`) invalida automáticamente su entrada porque el hash cambia. Capacidad 8 cubre el caso típico (natal + partner) con holgura. Test nuevo `natal_cache_hits_are_faster` valida que `compose` con offset_minutes repetido es más rápido que con offset distinto (HIT vs MISS): 9 tests engine, todos verdes. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
//! memoria), y como es read-only se puede leer en paralelo desde varios
|
||||
//! cómputos.
|
||||
|
||||
use std::sync::OnceLock;
|
||||
use std::sync::{Arc, OnceLock};
|
||||
use std::time::Instant;
|
||||
|
||||
use eternal_astrology::{
|
||||
@@ -228,18 +228,30 @@ fn build_eternal_inputs(
|
||||
Ok((birth_e, config_e, observer))
|
||||
}
|
||||
|
||||
/// Computa solo la `NatalChart` (sin construir RenderModel). Útil para
|
||||
/// pipelines compuestas (transits, sinastría) que necesitan el natal
|
||||
/// crudo para correr `find_synastry_aspects`.
|
||||
/// Computa la `NatalChart` consultando primero el LRU cache global.
|
||||
/// Útil para pipelines compuestas (transits, sinastría, composite) que
|
||||
/// computan la misma carta natal del partner en cada render — bajo
|
||||
/// drag de sliders se llama decenas de veces seguidas con inputs
|
||||
/// idénticos.
|
||||
///
|
||||
/// La clave incluye todos los campos de `StoredBirthData` y
|
||||
/// `StoredChartConfig` que afectan el cómputo; editar la carta invalida
|
||||
/// automáticamente la entrada.
|
||||
fn compute_natal_chart(
|
||||
chart: &Chart,
|
||||
offset_minutes: i64,
|
||||
) -> Result<(NatalChart, ChartConfig, Observer), EngineError> {
|
||||
) -> Result<(Arc<NatalChart>, ChartConfig, Observer), EngineError> {
|
||||
let (birth_e, config_e, observer) = build_eternal_inputs(chart, offset_minutes)?;
|
||||
let key = crate::natal_cache::key_for(&chart.birth_data, &chart.config, offset_minutes);
|
||||
if let Some(cached) = crate::natal_cache::get(key) {
|
||||
return Ok((cached, config_e, observer));
|
||||
}
|
||||
let session = session()?;
|
||||
let natal = NatalChart::compute(&birth_e, &config_e, session)
|
||||
.map_err(|e| EngineError::Eternal(format!("NatalChart::compute: {:?}", e)))?;
|
||||
Ok((natal, config_e, observer))
|
||||
let arc = Arc::new(natal);
|
||||
crate::natal_cache::insert(key, arc.clone());
|
||||
Ok((arc, config_e, observer))
|
||||
}
|
||||
|
||||
/// Composición principal: natal + overlays pedidos. Es la función que
|
||||
|
||||
Reference in New Issue
Block a user