wandres.dev
GRANO FINO Y GRANO GRUESO · La distinción fundamental

El coste asintótico real de cada modelo

Los órdenes de magnitud de las dos familias en actualización, arranque y memoria, con las constantes que las comparativas ocultan y los tres regímenes donde el ganador cambia.

⏱ 17 min

Las comparativas de frameworks miden un caso concreto y lo presentan como una verdad general. Con las dos implementaciones de las lecciones anteriores delante podemos hacer algo más útil: escribir el coste de cada operación en función de las magnitudes que importan, ver dónde se cruzan las curvas, y entender por qué la misma pareja de motores puede dar resultados opuestos según qué midas.

🎯 Al terminar esta lección sabrás
  • Escribir el coste de actualización, arranque y memoria de cada modelo.
  • Identificar las magnitudes que gobiernan cada coste.
  • Reconocer las constantes ocultas que dominan en tamaños realistas.
  • Determinar en qué régimen se encuentra una aplicación concreta.

Las magnitudes

Para hablar con precisión necesitamos nombres. Llamemos N al número total de nodos de la vista, K al número de nodos afectados por un cambio concreto, D al número de dependencias —aristas— del grafo, y S al tamaño del subárbol que se reejecuta en el modelo de reconciliación.

Con N grande y K pequeño estamos en el caso que favorece al grano fino. Con K cercano a N —cambiar el conjunto de datos entero— los dos modelos convergen. Casi toda la discusión pública ignora que K es un parámetro y no una constante.

Los tres costes

Coste por actualización

operación reconciliación grano fino
reejecutar lógica O de S O de K
producir descripción O de S no existe
comparar O de S no existe
mutar el mundo O de K O de K
mantener aristas no existe O de aristas de los K nodos

La conclusión asintótica es directa: la reconciliación es O(S), el grano fino es O(K). Y como S es siempre mayor o igual que K —el subárbol reejecutado contiene por fuerza a los nodos que de verdad cambiaron—, el grano fino nunca es asintóticamente peor en actualización.

Lo que la tabla no dice es cuánto valen las constantes. Un nodo reejecutado en reconciliación cuesta una llamada, unas cuantas asignaciones de objeto y unas comparaciones de propiedad: son operaciones baratísimas sobre memoria contigua y bien predichas por el motor de JavaScript. Un nodo actualizado en grano fino cuesta una llamada, más el desatar y tejer de sus aristas, más varias operaciones sobre tablas hash. Con S igual a diez y K igual a ocho, el grano fino puede perder a pesar de tener mejor orden.

ℹ️
La barrera esta alrededor de un factor diez

Como regla de campo que aguanta bien: el grano fino empieza a ganar de forma inequívoca cuando S supera a K en al menos un orden de magnitud. Por debajo de eso, las constantes deciden y hay que medir. Por eso una lista de diez mil filas con una modificada es una demostración devastadora, y un formulario de doce campos no distingue nada.

Coste de arranque

Aquí se invierten los papeles y se habla mucho menos de ello.

La reconciliación arranca en O(N): reejecuta todo, produce la descripción entera, y como no hay árbol anterior aplica todos los parches como creaciones. No mantiene ninguna estructura persistente más allá del árbol resultante.

El grano fino arranca en O(N + D): crea los nodos del DOM, y además crea un objeto reactivo por cada punto variable, ejecuta cada uno una primera vez y teje todas sus aristas. El término D no aparece en el otro modelo en absoluto.

En pantallas grandes de primera carga esto es medible y va en contra del grano fino. Que en la práctica no se note tanto se debe a un factor externo: los motores de grano fino suelen ir acompañados de compiladores que generan el DOM por clonación de plantillas en lugar de por llamadas individuales, lo que ahorra en el término N lo que pierden en el D. Es una ventaja de la herramienta, no del modelo reactivo.

Coste de memoria

La reconciliación mantiene el árbol descriptivo anterior: O(N) objetos, que además son basura de vida corta muy amigable para el recolector generacional.

El grano fino mantiene el grafo: O(N + D) objetos, de vida larga, que sobreviven a las colecciones jóvenes y acaban en el espacio antiguo. La diferencia no está solo en el tamaño sino en el perfil: memoria persistente que hay que trazar en cada ciclo de marcado, frente a memoria efímera que se descarta casi gratis.

flowchart TB
subgraph reconciliacion
A1[memoria efimera por ciclo] --> A2[recolector joven barato]
end
subgraph granofino
B1[grafo persistente] --> B2[recolector viejo mas caro]
B1 --> B3[coste por arista mantenida]
end
style A1 fill:#89b4fa,color:#11111b
style A2 fill:#a6e3a1,color:#11111b
style B1 fill:#cba6f7,color:#11111b
style B2 fill:#f9e2af,color:#11111b
style B3 fill:#f9e2af,color:#11111b

Los tres regímenes

Con las tres tablas se pueden delimitar tres regímenes donde el ganador es distinto, y en cuál estés depende de tu aplicación, no de tus preferencias.

Régimen de actualización intensiva. N grande, K pequeño, muchas actualizaciones por sesión. Paneles de datos en vivo, editores, visualizaciones, juegos. El grano fino gana claramente y la ventaja crece con N. El coste de arranque se amortiza en los primeros segundos.

Régimen de arranque dominante. N grande, pocas actualizaciones, la métrica que importa es el tiempo hasta interactivo. Páginas de contenido, comercio electrónico, cualquier cosa donde el usuario llega, mira y se va. El grano fino paga el grafo y no lo amortiza; la ventaja, si la hay, viene del compilador y no del modelo.

Régimen de reemplazo masivo. K cercano a N: cambiar de página, cargar un conjunto de datos nuevo, filtrar de mil a tres elementos. Los dos modelos hacen esencialmente el mismo trabajo, y el grano fino paga además desatar y volver a tejer todas las aristas afectadas. Aquí la reconciliación puede ganar, y es el caso que ninguna comparativa mide.

Optimizas para el percentil, no para la media

La conversación sobre modelos reactivos casi siempre compara medias, y las medias son irrelevantes para la experiencia percibida. Lo que un usuario nota no es que la actualización media tarde ocho milisegundos en lugar de doce: nota el fotograma de ciento veinte milisegundos que aparece una vez cada tanto. Y las dos familias tienen perfiles de cola radicalmente distintos. La reconciliación produce un coste proporcional al subárbol invalidado, con una varianza acotada: nunca es gratis, nunca es catastrófico, y su peor caso es predecible. El grano fino produce un coste casi nulo la mayor parte del tiempo, con picos ocasionales cuando una escritura cae en un nodo del que cuelga media aplicación y provoca la reconstrucción de todo un subgrafo, incluida la de sus aristas. La distribución del grano fino tiene mejor mediana y peor cola. Esto tiene una consecuencia operativa concreta: si tu métrica es INP —que es un percentil alto por definición— optimizar el caso medio del grano fino puede no mover la aguja en absoluto, mientras que localizar y partir el único nodo responsable del pico sí la mueve entera. La herramienta correcta para eso no es una comparativa de frameworks: es un perfilador sobre tu propio grafo, buscando el nodo con más descendientes.

Cómo situar tu aplicación

Tres preguntas bastan para clasificarte, y las tres se responden midiendo.

Cuántas actualizaciones ocurren por sesión frente a cuántos nodos se crean. Si la proporción es de diez a uno o mayor, estás en el primer régimen.

Cuál es el valor típico de K dividido entre S. Si el subárbol que se invalida es mucho mayor que lo que de verdad cambia, hay margen; si son parecidos, no lo hay.

Cuánto de tu presupuesto de tiempo está en producir la vista y cuánto en el layout y el pintado del navegador. Si el segundo domina —y en interfaces con mucho CSS domina más de lo que la gente cree—, cambiar de modelo reactivo no moverá nada, porque ese coste lo pagan los dos por igual.

⚔️ Encuentra el punto de cruce
  1. Implementa la misma lista con los dos motores de las lecciones anteriores.
  2. Mide el tiempo de una actualización para N igual a diez, cien, mil y diez mil, con K fijo en uno.
  3. Localiza el N a partir del cual el grano fino gana e interpreta la constante que estabas pagando por debajo.
  4. Repite con K igual a N y comprueba cuál gana ahora.