Hola a todos Cada vez queda menos tiempo hasta el lanzamiento del curso
"Seguridad de los sistemas de informaci贸n" , por lo que hoy continuamos compartiendo publicaciones dedicadas al lanzamiento de este curso. Por cierto, esta publicaci贸n es una continuaci贸n de estos dos art铆culos:
鈥淔undamentos de los motores de JavaScript: formularios generales y almacenamiento en cach茅 en l铆nea. Parte 1 " ,
" Conceptos b谩sicos de los motores de JavaScript: formularios generales y almacenamiento en cach茅 en l铆nea. Parte 2 " .
El art铆culo describe los conceptos b谩sicos clave. Son comunes a todos los motores de JavaScript, y no solo al
V8 en el que est谩n trabajando los autores (
Benedict y
Matthias ). Como desarrollador de JavaScript, puedo decir que una comprensi贸n m谩s profunda de c贸mo funciona el motor de JavaScript lo ayudar谩 a descubrir c贸mo escribir c贸digo eficiente.

En un
art铆culo anterior, discutimos c贸mo los motores JavaScript optimizan el acceso a objetos y matrices utilizando formularios y cach茅s en l铆nea. En este art铆culo, veremos c贸mo optimizar los compromisos de la tuber铆a y acelerar el acceso a las propiedades del prototipo.
Nota: si prefiere ver presentaciones que leer art铆culos, mire este video . De lo contrario, s谩ltelo y siga leyendo.
Niveles de optimizaci贸n y compensacionesLa 煤ltima vez, descubrimos que todos los motores JavaScript modernos, de hecho, tienen la misma tuber铆a:

Tambi茅n nos dimos cuenta de que a pesar del hecho de que las tuber铆as de alto nivel de motor a motor son similares en estructura, hay una diferencia en la tuber铆a de optimizaci贸n. 驴Por qu茅 es esto as铆? 驴Por qu茅 algunos motores tienen m谩s niveles de optimizaci贸n que otros? La cuesti贸n es hacer un compromiso entre una transici贸n r谩pida a la etapa de ejecuci贸n del c贸digo o pasar un poco m谩s de tiempo para ejecutar el c贸digo con un rendimiento 贸ptimo.

El int茅rprete puede generar r谩pidamente bytecode, pero bytecode por s铆 solo no es lo suficientemente eficiente en t茅rminos de velocidad. Involucrar a un compilador de optimizaci贸n en este proceso pasa una cierta cantidad de tiempo, pero permite un c贸digo de m谩quina m谩s eficiente.
Echemos un vistazo a c贸mo el V8 maneja esto. Recuerde que en V8 el int茅rprete se llama Ignition y se considera el int茅rprete m谩s r谩pido entre los motores existentes (en materia de velocidad de ejecuci贸n de bytecode sin formato). El compilador de optimizaci贸n en V8 se llama TurboFan, y es 茅l quien genera un c贸digo de m谩quina altamente optimizado.

La compensaci贸n entre el retraso de inicio y la velocidad de ejecuci贸n es la raz贸n por la cual algunos motores de JavaScript prefieren agregar niveles de optimizaci贸n adicionales entre los pasos. Por ejemplo, SpiderMonkey agrega un nivel de l铆nea de base entre su int茅rprete y el compilador de optimizaci贸n completo de IonMonkey:

El int茅rprete genera r谩pidamente bytecode, pero el bytecode en s铆 es relativamente lento. La l铆nea de base genera c贸digo un poco m谩s, pero proporciona un rendimiento mejorado en tiempo de ejecuci贸n. Finalmente, el compilador de optimizaci贸n IonMonkey pasa la mayor cantidad de tiempo generando c贸digo de m谩quina, pero dicho c贸digo es extremadamente eficiente.
Veamos un ejemplo espec铆fico y veamos c贸mo las tuber铆as de varios motores abordan este problema. Aqu铆 en el bucle activo, a menudo se repite el mismo c贸digo.
let result = 0; for (let i = 0; i < 4242424242; ++i) { result += i; } console.log(result);
V8 comienza iniciando el c贸digo de bytes en el int茅rprete de encendido. En alg煤n momento, el motor determina que el c贸digo est谩 activo y lanza la interfaz TurboFan, que integra datos de creaci贸n de perfiles y crea una representaci贸n de m谩quina b谩sica del c贸digo. Luego se env铆a al optimizador TurboFan en otro hilo para una mejora adicional.

Mientras la optimizaci贸n est谩 en curso, V8 contin煤a ejecutando c贸digo en Ignition. En alg煤n momento, cuando el optimizador ha terminado y hemos recibido el c贸digo de m谩quina ejecutable, inmediatamente pasa a la etapa de ejecuci贸n.
SpyderMonkey tambi茅n comienza la ejecuci贸n de bytecode en el int茅rprete. Pero tiene un nivel de l铆nea de base adicional, lo que significa que primero se env铆a el c贸digo activo. El compilador de l铆nea de base genera c贸digo de l铆nea de base en el hilo principal y contin煤a la ejecuci贸n al final de su generaci贸n.

Si el c贸digo de l铆nea de base se ha estado ejecutando durante alg煤n tiempo, SpiderMonkey finalmente lanza la interfaz IonMonkey (interfaz IonMonkey) y ejecuta el optimizador, el proceso es muy similar al V8. Todo esto contin煤a funcionando al mismo tiempo en Baseline, mientras que IonMonkey se dedica a la optimizaci贸n. Finalmente, cuando el optimizador termina su trabajo, se ejecuta el c贸digo optimizado en lugar del c贸digo de l铆nea de base.
La arquitectura de Chakra es muy similar a SpiderMonkey, pero Chakra est谩 tratando de ejecutar m谩s procesos al mismo tiempo para evitar bloquear el hilo principal. En lugar de ejecutar cualquier parte del compilador en el hilo principal, Chakra copia el bytecode y los datos de perfil que necesita el compilador y los env铆a al proceso dedicado del compilador.

Cuando el c贸digo generado est谩 listo, el motor ejecuta este c贸digo SimpleJIT en lugar del c贸digo de bytes. Lo mismo sucede con FullJIT. La ventaja de este enfoque es que la pausa que ocurre durante la copia suele ser mucho m谩s corta que iniciar un compilador completo (frontend). Por otro lado, este enfoque tiene un inconveniente. Se basa en el hecho de que la copia heur铆stica puede omitir alguna informaci贸n que ser谩 necesaria para la optimizaci贸n, por lo que podemos decir que, en cierta medida, la calidad del c贸digo se sacrifica en aras de acelerar el trabajo.
En JavaScriptCore, todos los compiladores de optimizaci贸n funcionan completamente en paralelo con la ejecuci贸n b谩sica de JavaScript. No hay fase de copia. En cambio, el hilo principal simplemente comienza a compilarse en otro hilo. Los compiladores utilizan un complejo esquema de bloqueo para acceder a los datos de creaci贸n de perfiles desde el hilo principal.

La ventaja de este enfoque es que reduce la cantidad de basura que aparece despu茅s de la optimizaci贸n en el hilo principal. La desventaja de este enfoque es que requiere resolver problemas complejos de subprocesos m煤ltiples y algunos costos de bloqueo para diversas operaciones.
Hablamos sobre las compensaciones entre la generaci贸n r谩pida de c贸digo mientras el int茅rprete est谩 en ejecuci贸n y la generaci贸n r谩pida de c贸digo utilizando el compilador de optimizaci贸n. Pero hay un compromiso m谩s, y se refiere al uso de la memoria. Para ilustrarlo, escrib铆 un programa simple de JavaScript que agrega dos n煤meros.
function add(x, y) { return x + y; } add(1, 2);
Mire el c贸digo de bytes que genera el int茅rprete de encendido para la funci贸n de agregar en V8.
StackCheck Ldar a1 Add a0, [0] Return
No se preocupe por el c贸digo de bytes, no es necesario que pueda leerlo. Aqu铆 es necesario prestar atenci贸n al hecho de que contiene
solo 4 instrucciones .
Cuando el c贸digo se calienta, TurboFan genera un c贸digo de m谩quina altamente optimizado, que se presenta a continuaci贸n:
leaq rcx,[rip+0x0] movq rcx,[rcx-0x37] testb [rcx+0xf],0x1 jnz CompileLazyDeoptimizedCode push rbp movq rbp,rsp push rsi push rdi cmpq rsp,[r13+0xe88] jna StackOverflow movq rax,[rbp+0x18] test al,0x1 jnz Deoptimize movq rbx,[rbp+0x10] testb rbx,0x1 jnz Deoptimize movq rdx,rbx shrq rdx, 32 movq rcx,rax shrq rcx, 32 addl rdx,rcx jo Deoptimize shlq rdx, 32 movq rax,rdx movq rsp,rbp pop rbp ret 0x18
Realmente hay muchos equipos aqu铆, especialmente en comparaci贸n con los cuatro que vimos en el c贸digo de bytes. En general, el c贸digo de bytes es mucho m谩s amplio que el c贸digo de m谩quina y, en particular, el c贸digo de m谩quina optimizado. Bytecode, por otro lado, es ejecutado por el int茅rprete, mientras que el c贸digo optimizado puede ser ejecutado directamente por el procesador.
Esta es una de las razones por las cuales los motores de JavaScript no solo "optimizan todo". Como vimos anteriormente, generar c贸digo de m谩quina optimizado lleva mucho tiempo y, por lo tanto, requiere m谩s memoria.
Para resumir: La raz贸n por la cual los motores de JavaScript tienen diferentes niveles de optimizaci贸n es para encontrar un compromiso entre la generaci贸n r谩pida de c贸digo usando el int茅rprete y la generaci贸n r谩pida de c贸digo usando el compilador de optimizaci贸n. Agregar m谩s niveles de optimizaci贸n le permite tomar decisiones m谩s informadas, en funci贸n del costo de la complejidad y los gastos generales adicionales durante la ejecuci贸n. Adem谩s, existe una compensaci贸n entre el nivel de optimizaci贸n y el uso de memoria. Es por eso que los motores de JavaScript intentan optimizar solo las funciones activas.
Optimizar el acceso a las propiedades del prototipoLa 煤ltima vez hablamos sobre c贸mo los motores JavaScript optimizan la carga de las propiedades de los objetos mediante formularios y cach茅s en l铆nea. Recuerde que los motores almacenan las formas de los objetos por separado de los valores del objeto.

Los formularios le permiten utilizar la optimizaci贸n utilizando cach茅s en l铆nea o circuitos integrados abreviados. Al trabajar juntos, los formularios y los circuitos integrados pueden acelerar el acceso repetido a las propiedades desde el mismo lugar en su c贸digo.

As铆 que la primera parte de la publicaci贸n lleg贸 a su fin, y sobre las clases y la programaci贸n de prototipos se puede encontrar en la
segunda parte . Tradicionalmente, estamos esperando sus comentarios y discusiones tormentosas, as铆 como lo invitamos a una
jornada de puertas abiertas en el curso "Seguridad de los sistemas de informaci贸n".