- En el motor V8, mejoraron el rendimiento de la función JSON.stringify a más del doble, logrando una serialización de datos más rápida
- Introdujeron una ruta de optimización para objetos sin efectos secundarios, omitiendo gran parte de la lógica defensiva de verificación y consiguiendo una mejora importante de velocidad en objetos de datos comunes
- En el procesamiento de cadenas, aplicaron métodos más avanzados a nivel de hardware y memoria, como la distinción entre 1 byte/2 bytes, uso de SIMD y cambios en la estructura del búfer temporal
- En el proceso de conversión numérica, reemplazaron el algoritmo Grisu3 por Dragonbox, lo que también permite conversiones más rápidas en las llamadas a
Number.toString()
- En algunos argumentos o formas vuelve a la ruta de serialización general, pero en la mayoría de los escenarios de desarrollo web se puede aprovechar la optimización automáticamente
Resumen general
JSON.stringify es una función clave para convertir datos en cadenas dentro de JavaScript
- Mejorar el rendimiento de esta función también impacta positivamente tareas muy importantes en la web, como solicitudes de red o almacenamiento en localStorage
- Gracias al trabajo de ingeniería más reciente en V8, la velocidad de esta función mejoró a más del doble, y aquí se presentan en detalle las principales optimizaciones
Ruta Fast Path sin efectos secundarios
- La clave de la optimización es aplicar una ruta rápida de serialización que solo puede usarse en situaciones sin efectos secundarios (side effects)
- En estos casos, el recorrido del objeto se hace con una estructura iterativa en lugar de recursiva, por lo que no se necesitan verificaciones de stack overflow y también es posible intentar serializar objetos más profundos
- Cuando el objeto de datos es simple, V8 usa este Fast Path en lugar de la lógica general más lenta, omitiendo muchas verificaciones y acelerando el proceso
Manejo de distintas representaciones de cadenas
- V8 almacena las cadenas de forma diferente según sean de 1 byte o 2 bytes (ASCII/no ASCII), y si aparece aunque sea un carácter no ASCII, todo se maneja como 2 bytes
- Para mejorar el rendimiento de la serialización de cadenas, compilaron versiones separadas del algoritmo según el tipo de cadena
- Como durante el procesamiento hay que verificar el tipo de instancia de la cadena, si se detecta una cadena de 2 bytes, el serializador apropiado de 2 bytes toma el control del estado
- Gracias a esto, la sobrecarga por cambiar entre rutas según la codificación de la cadena es prácticamente nula
- El resultado se genera creando por separado búferes de 1 byte y 2 bytes, que luego simplemente se combinan al final
Optimización de serialización de cadenas con SIMD
- Las cadenas de JavaScript pueden incluir caracteres que necesitan escape al serializarse como JSON
- Las cadenas largas se revisan varios bytes a la vez usando instrucciones SIMD de hardware (como ARM64 Neon)
- Las cadenas cortas usan un enfoque SWAR, procesando varios caracteres al mismo tiempo mediante operaciones de bits en registros de propósito general
- En cualquiera de los dos casos, la mayoría de las veces es posible copiar toda la cadena rápidamente sin transformaciones adicionales
Se agregó Express Lane (ruta ultrarrápida)
- Incluso dentro del Fast Path, se preparó una Express Lane para poder serializar copiando solo las claves, sin trabajo repetitivo como verificaciones de propiedades
- Aprovechando los flags de hidden class del objeto, si las claves no tienen Symbol, todas son enumerable y ya pueden serializarse sin necesidad de escape, se marca como
fast-json-iterable
- Al serializar otros objetos con la misma hidden class, las claves se copian directamente sin verificaciones adicionales
- Esta técnica también se aplica en
JSON.parse para comparaciones rápidas de claves
Un algoritmo más rápido de double-to-string
- El proceso de convertir números a cadenas también es frecuente y complejo
- Se reemplazó el algoritmo Grisu3 por Dragonbox, lo que mejora el rendimiento en las llamadas generales a
Number.prototype.toString()
Optimización de la estructura del búfer temporal
- Antes, al construir cadenas se usaba un único búfer continuo, lo que provocaba una sobrecarga por copiar todo el contenido cada vez que faltaba espacio
- El nuevo enfoque usa una estructura de búfer segmentado, uniendo varios búferes pequeños según sea necesario
- Gracias a esto, cuando no hay suficiente espacio, solo se asigna un nuevo búfer sin necesidad de copiar todo
Limitaciones
- El Fast Path solo funciona para serialización simple de datos
- Si no se cumplen las siguientes condiciones, se usa la ruta general
- No se puede usar el argumento replacer ni space (sin Pretty-Print ni transformaciones)
- Debe ser un objeto simple sin método personalizado toJSON
- Si hay propiedades basadas en índice, pasa a la ruta lenta
- No maneja cadenas especiales como ConsString
- En la mayoría de usos comunes, como serialización de datos, generación de respuestas de API o caché de configuración, la optimización se aplica automáticamente
Conclusión
- Al replantear el enfoque en todas las áreas, desde el diseño básico de
JSON.stringify hasta el manejo de memoria y caracteres, lograron un aumento de velocidad de más de 2x en el benchmark JetStream2
- Estas mejoras ya pueden experimentarse en V8 versión 13.8 (Chrome 138) o superior
Aún no hay comentarios.