Ingeniería de Rendimiento: Optimización Zero-Copy IPC
Cómo redujimos las asignaciones de heap en un 80% en la ruta crítica de OmniMon usando IPC zero-copy, caché con Arc y ordenamiento por referencia.
El Costo de Clonar
El puente IPC de OmniMon transfiere métricas del sistema desde el backend Rust al frontend Svelte cada 2 segundos. Con más de 500 procesos activos, cada transferencia clonaba cientos de structs — creando presión innecesaria en el heap y pausas de GC.
Nos propusimos eliminar cada asignación redundante.
Optimización 1: Cachear hw.memsize al Inicio
Antes: OmniMon generaba un subproceso sysctl -n hw.memsize cada 2 segundos para leer la memoria total del sistema.
Después: Un OnceLock<Option<u64>> cachea el valor en la primera llamada. La memoria total del sistema no cambia en tiempo de ejecución.
Impacto: Elimina 0.5 spawns de subprocesos por segundo.
Optimización 2: Consumir Caché del Watcher
Antes: Funciones como top_processes_by_memory() creaban instancias frescas de System::new_all() — activando escaneos completos del sistema.
Después: Todas las funciones de métricas leen primero del caché existente SystemState del watcher, con fallback para casos extremos.
Impacto: ~60% de reducción en overhead de syscalls de CPU en la ruta crítica.
Optimización 3: Ordenamiento por Referencia
Antes: La función get_metrics() clonaba el vector completo de más de 500 procesos, lo ordenaba y luego lo truncaba a los 100 principales.
Después: Crea un Vec<&CachedProcessInfo> de referencias, ordena in-place y luego clona solo las 100 entradas principales.
Impacto: ~80% menos asignaciones de heap por llamada IPC (~400 clones de struct eliminados).
Optimización 4: Caché de Pestañas con Arc
Antes: Cada lectura del caché de pestañas del navegador clonaba todo el Vec<BrowserTab> más todos los campos String — una operación O(n) con ~60 asignaciones de strings.
Después: El caché almacena Arc<Vec<BrowserTab>>. Las lecturas clonan solo el puntero Arc — un único incremento atómico, O(1).
Impacto: Elimina ~60 asignaciones de String por llamada a get_browser_tabs().
Optimización 5: Seguridad ante Panics
Antes: Una macro unreachable!() al final de send_with_retry() podía crashear toda la app si el ciclo de reintentos salía inesperadamente.
Después: Reemplazado con un Err("Unexpected exit from retry loop") explícito — propagación de error elegante en lugar de un panic en producción.
Optimizaciones del Frontend
La capa Svelte recibió optimizaciones equivalentes:
- Virtual scrolling refinado para mantener 60 FPS con más de 2000 procesos
- Búsqueda con debounce (150ms) previene filtrado O(n) por cada tecla
- Reactividad basada en stores evita re-renders innecesarios de componentes
Resultados
| Métrica | Antes | Después |
|---|---|---|
| Asignaciones de heap por llamada IPC | ~500 | ~100 |
| Spawns de subprocesos / segundo | 0.5 | 0 |
| Lecturas de caché de pestañas | O(n) | O(1) |
| Clones en ordenamiento de procesos | 500+ | 100 |
Estas optimizaciones se acumulan: OmniMon ahora maneja más de 2000 procesos simultáneos con latencia de UI imperceptible.