Lanzamiento de V8 v7.4
Cada seis semanas, creamos una nueva rama de V8 como parte de nuestro proceso de lanzamiento. Cada versión se deriva del maestro de Git de V8 inmediatamente antes de un hito de Chrome Beta. Hoy nos complace anunciar nuestra nueva rama, V8 versión 7.4, que está en beta hasta su lanzamiento en coordinación con Chrome 74 Stable en varias semanas. V8 v7.4 está repleto de todo tipo de beneficios para desarrolladores. Esta publicación ofrece un adelanto de algunos de los aspectos destacados en anticipación al lanzamiento.
V8 sin JIT
V8 ahora admite la ejecución de JavaScript sin asignar memoria ejecutable en tiempo de ejecución. Se puede encontrar información detallada sobre esta característica en la publicación dedicada del blog.
Hilos/Atómicos de WebAssembly lanzados
Los hilos/atómicos de WebAssembly ahora están habilitados en sistemas operativos que no sean Android. Esto concluye la prueba de origen/vista previa que habilitamos en V8 v7.0. Un artículo de Fundamentos de la Web explica cómo usar Atómicos de WebAssembly con Emscripten.
Esto desbloquea el uso de múltiples núcleos en la máquina de un usuario a través de WebAssembly, permitiendo nuevos casos de uso intensivos en computación en la web.
Rendimiento
Llamadas más rápidas con desajuste de argumentos
En JavaScript es perfectamente válido llamar a funciones con muy pocos o demasiados parámetros (es decir, pasar menos o más parámetros de los declarados formalmente). Lo primero se llama subaplicación, y lo segundo se llama sobreaplicación. En caso de subaplicación, los parámetros formales restantes se asignan como undefined
, mientras que en caso de sobreaplicación, los parámetros superfluos se ignoran.
Sin embargo, las funciones de JavaScript aún pueden acceder a los parámetros reales mediante el objetoarguments
, usando parámetros rest, o incluso utilizando la propiedad no estándar Function.prototype.arguments
en funciones en modo descuidado. Como resultado, los motores de JavaScript deben proporcionar un medio para acceder a los parámetros reales. En V8, esto se realiza mediante una técnica llamada adaptación de argumentos, que proporciona los parámetros reales en caso de subaplicación o sobreaplicación. Desafortunadamente, la adaptación de argumentos tiene un costo de rendimiento y es comúnmente necesaria en frameworks modernos de front-end y middleware (es decir, muchas APIs con parámetros opcionales o listas de argumentos variables).
Hay escenarios donde el motor sabe que la adaptación de argumentos no es necesaria ya que los parámetros reales no pueden ser observados, a saber, cuando el callee es una función en modo estricto y no utiliza arguments
ni parámetros rest. En estos casos, V8 ahora omite completamente la adaptación de argumentos, reduciendo la sobrecarga de llamada en hasta 60%.
El gráfico muestra que ya no hay sobrecarga, incluso en caso de desajuste de argumentos (asumiendo que el callee no puede observar los argumentos reales). Para más detalles, consulta el documento de diseño.
Rendimiento mejorado de los accesores nativos
El equipo de Angular descubrió que llamar a accesores nativos directamente mediante sus funciones get
respectivas era significativamente más lento en Chrome que el acceso a propiedades monomórfico o incluso megamórfico. Esto se debía a tomar la vía lenta en V8 para llamar a accesores de DOM mediante Function#call()
, en vez de la vía rápida que ya existía para accesos a propiedades.
Logramos mejorar el rendimiento de las llamadas a los accesores nativos, haciéndolas significativamente más rápidas que el acceso a propiedades megamórficas. Para más información, consulta V8 issue #8820.
Rendimiento del analizador
En Chrome, los scripts lo suficientemente grandes se analizan en "transmisión" en hilos de trabajo mientras se descargan. En esta versión identificamos y solucionamos un problema de rendimiento con la decodificación personalizada de UTF-8 utilizada por el flujo fuente, lo que llevó a un análisis en transmisión un 8% más rápido en promedio.
Encontramos un problema adicional en el preanalizador de V8, que generalmente funciona en un hilo de trabajo: los nombres de propiedades se deduplicaban innecesariamente. Eliminar esta deduplicación mejoró el analizador en transmisión en otro 10.5%. Esto también mejora el tiempo de análisis en el hilo principal de scripts que no se transmiten, como scripts pequeños y scripts en línea.
Memoria
Eliminación de bytecode
El bytecode compilado a partir de código fuente de JavaScript ocupa una parte significativa del espacio del heap de V8, típicamente alrededor del 15%, incluyendo metadatos relacionados. Hay muchas funciones que solo se ejecutan durante la inicialización o que se usan raramente después de haberse compilado.
Para reducir la sobrecarga de memoria de V8, hemos implementado soporte para eliminar bytecode compilado de funciones durante la recolección de basura si no se han ejecutado recientemente. Para habilitar esto, llevamos un registro de la antigüedad del bytecode de una función, incrementando la antigüedad durante las recolecciones de basura y reiniciándola a cero cuando la función se ejecuta. Cualquier bytecode que cruce un umbral de envejecimiento es elegible para ser recolectado por la próxima recolección de basura, y la función se reinicia para recompilar su bytecode de manera perezosa si se ejecuta nuevamente en el futuro.
Nuestros experimentos con la eliminación de bytecode muestran que proporciona ahorros significativos de memoria para los usuarios de Chrome, reduciendo la cantidad de memoria en el heap de V8 entre un 5–15% sin afectar el rendimiento ni aumentar significativamente el tiempo de CPU dedicado a compilar código JavaScript.
Eliminación de bloques básicos de bytecode muertos
El compilador de bytecode Ignition intenta evitar generar código que sabe que está muerto, como el código después de una declaración return
o break
:
return;
deadCall(); // omitido
Sin embargo, anteriormente esto se hacía de manera oportunista para declaraciones de terminación en una lista de declaraciones, por lo que no se tenían en cuenta otras optimizaciones, como atajos en condiciones que se sabe que son verdaderas:
if (2.2) return;
deadCall(); // no omitido
Intentamos resolver esto en V8 v7.3, pero todavía a nivel de declaración, lo que no funcionaría cuando el flujo de control se volviera más complejo, por ejemplo:
do {
if (2.2) return;
break;
} while (true);
deadCall(); // no omitido
El deadCall()
anterior estaría al comienzo de un nuevo bloque básico, que a nivel de declaración es alcanzable como objetivo para declaraciones break
en el bucle.
En V8 v7.4, permitimos que bloques básicos completos se conviertan en muertos, si ningún bytecode Jump
(la primitiva principal de flujo de control de Ignition) se refiere a ellos. En el ejemplo anterior, el break
no se emite, lo que significa que el bucle no tiene declaraciones break
. Entonces, el bloque básico que comienza con deadCall()
no tiene saltos referentes, y por lo tanto también se considera muerto. Aunque no esperábamos que esto tuviera un gran impacto en el código de usuario, es particularmente útil para simplificar varios desazúcares, como generadores, for-of
y try-catch
, y en particular elimina una clase de errores donde los bloques básicos podrían "resucitar" declaraciones complejas a mitad de su implementación.
Características del lenguaje JavaScript
Campos privados de clases
V8 v7.2 agregó soporte para la sintaxis de campos públicos en clases. Los campos de clase simplifican la sintaxis de clases al evitar la necesidad de funciones constructoras solo para definir propiedades de instancia. A partir de V8 v7.4, puedes marcar un campo como privado anteponiéndole un prefijo #
.
class IncreasingCounter {
#count = 0;
get value() {
console.log('¡Obteniendo el valor actual!');
return this.#count;
}
increment() {
this.#count++;
}
}
A diferencia de los campos públicos, los campos privados no son accesibles fuera del cuerpo de la clase:
const counter = new IncreasingCounter();
counter.#count;
// → SyntaxError
counter.#count = 42;
// → SyntaxError
Para más información, lee nuestro explicador sobre campos públicos y privados en clases.
Intl.Locale
Las aplicaciones de JavaScript generalmente utilizan cadenas como 'en-US'
o 'de-CH'
para identificar configuraciones regionales. Intl.Locale
ofrece un mecanismo más potente para manejar configuraciones regionales, y permite extraer fácilmente preferencias específicas de la región como el idioma, el calendario, el sistema de numeración, el ciclo horario, y más.
const locale = new Intl.Locale('es-419-u-hc-h12', {
calendar: 'gregory'
});
locale.language;
// → 'es'
locale.calendar;
// → 'gregory'
locale.hourCycle;
// → 'h12'
locale.region;
// → '419'
locale.toString();
// → 'es-419-u-ca-gregory-hc-h12'
Gramática de Hashbang
Los programas de JavaScript ahora pueden comenzar con #!
, un llamado hashbang. El resto de la línea que sigue al hashbang se trata como un comentario de una sola línea. Esto coincide con el uso de facto en anfitriones de línea de comandos de JavaScript, como Node.js. Ahora el siguiente es un programa de JavaScript sintácticamente válido:
#!/usr/bin/env node
console.log(42);
API de V8
Por favor, use git log branch-heads/7.3..branch-heads/7.4 include/v8.h
para obtener una lista de los cambios en la API.
Los desarrolladores con un checkout activo de V8 pueden usar git checkout -b 7.4 -t branch-heads/7.4
para experimentar con las nuevas funciones en V8 v7.4. Alternativamente, puedes suscribirte al canal Beta de Chrome y probar las nuevas funciones pronto.