El camino hacia la optimización del rendimiento del compilador de programas pequeños

Autor | marco

Introducción

El compilador de subprogramas es un módulo de compilación en las herramientas de desarrollo de Baidu, que se utiliza para convertir el código del subprograma en código de tiempo de ejecución. Debido al desarrollo empresarial, la versión anterior del compilador tenía problemas con una compilación lenta y un alto uso de memoria. Hicimos una reconstrucción a gran escala del compilador, adoptamos una arquitectura de desarrollo propio y realizamos muchas optimizaciones, como subprocesos múltiples, código. almacenamiento en caché y mapa fuente. El rendimiento y el uso de la memoria se han mejorado enormemente. El texto completo presenta las ideas de diseño y los métodos de optimización de la nueva versión del compilador, así como algunos puntos técnicos que se pueden utilizar en herramientas de empaquetado generales.

El texto completo tiene 6629 palabras y el tiempo estimado de lectura es de 17 minutos.

01 Prefacio

Los minicompiladores de programas son necesarios en todas las etapas del desarrollo, vista previa y lanzamiento del miniprograma, por lo que el rendimiento del compilador afectará directamente la eficiencia del desarrollo del desarrollador y la experiencia de uso de las herramientas del desarrollador.

Los desarrolladores se han quejado de que la versión antigua del compilador (basada en webpack4) es muy lenta al crear proyectos grandes y utiliza mucha memoria. Después de mucha investigación y desarrollo, finalmente adoptamos una arquitectura completamente desarrollada por nosotros mismos para la nueva compilación, hicimos muchas optimizaciones para la construcción de proyectos de programas pequeños y básicamente resolvimos los problemas de la compilación anterior.

La siguiente figura es una comparación de los tiempos de construcción de algunos proyectos:

La nueva versión del compilador logra una mejora de rendimiento de 2 a 7 veces en comparación con la versión anterior y admite funciones como compilación en tiempo real y recarga en caliente, ocupa menos memoria y crea mejores productos.

A continuación se presenta la ruta de desarrollo del nuevo compilador desde los aspectos de la selección del marco, el principio de funcionamiento del nuevo compilador, el rendimiento y los métodos de optimización del producto.

02 Selección de cuadro

Al diseñar una nueva versión del compilador, es necesario aclarar el problema actual: el rendimiento y priorizar la resolución de problemas de rendimiento. También se implementan otras técnicas e ideas nuevas que son útiles para el compilador.

La versión anterior del compilador basado en webpack4 tiene los siguientes problemas:

  • Los grandes proyectos se construyen con demasiada lentitud.

  • El inicio del desarrollo es lento y la compilación incremental es lenta. Solo admite el almacenamiento en caché del cargador y los paquetes sin almacenamiento en caché también son más lentos.

  • El desarrollo de extensiones basado en webpack4 requiere parchear algunos módulos para que funcionen, lo que dificulta el mantenimiento.

  • Parte del proceso del paquete webpack no se puede optimizar para la estructura del código del subprograma y hay compilaciones no válidas.

Objetivos de diseño recién compilados:

  • Velocidad de compilación completa más rápida y eliminación del proceso de compilación no válido del paquete web.

  • Admite el almacenamiento en caché completo para acelerar la compilación incremental y por primera vez.

  • Admite la compilación en tiempo real, lo que reduce el tiempo de inicio del desarrollador y de compilación secundaria.

  • Admite aceleración de compilación de subprocesos múltiples y recarga de páginas en caliente.

  • Optimice la estructura del producto y reduzca el volumen del producto.

2.1 Herramientas de construcción convencionales

A continuación se presentan las principales herramientas de construcción front-end que hemos investigado. Cada herramienta tiene escenarios, ventajas y desventajas aplicables.

Al diseñar una nueva versión de la arquitectura del compilador, vale la pena consultar los conceptos de diseño y las características técnicas de otras herramientas de compilación.

Proceso de construcción del paquete web:

Ventajas de Webpack : funciones completas, comunidad activa, gran capacidad de configuración y gran escalabilidad.

Desventajas del paquete web : configuración compleja, velocidad de construcción lenta y dificultad en el desarrollo secundario.

Proceso de construcción de parcelas:

Ventajas de Parcel : No se requiere configuración, velocidad de construcción rápida, soporte nativo para subprocesos múltiples y caché completo, el intercambio de datos entre subprocesos múltiples se realiza a través de lmdb, lo que evita la sobrecarga de comunicación entre subprocesos.

Desventajas de Parcel : ecosistema pequeño, personalización limitada, uso extensivo de complementos de Node y mala compatibilidad.

Proceso de construcción de Vite:

Ventajas de Vite : configuración relativamente simple, compilación bajo demanda, inicio rápido y una buena experiencia durante el desarrollo.

Desventajas de Vite : el ecosistema es pequeño y hay dos conjuntos de procesos de compilación para desarrollo y lanzamiento.

Otras plataformas de mini programas:

  • WeChat crea pequeños programas basados ​​en módulos gulp y C++, y preconstruye módulos npm, lo que proporciona un mejor rendimiento y experiencia de desarrollo.

  • Alipay crea pequeños programas basados ​​en webpack y utiliza esbuild para acelerar la compresión de código.

  • El miniprograma Douyin utiliza un compilador de desarrollo propio y el proceso de construcción es relativamente simple.

2.2 Nueva versión del compilador

Al diseñar el nuevo marco de compilación, extrajimos lecciones del flujo de trabajo de las herramientas de empaquetado convencionales, combinado con las características del código del mini programa, y ​​decidimos no crear una herramienta de empaquetado universal, enfocándonos en optimizar el rendimiento del empaquetado del mini programa.

Finalmente, elegimos la solución del compilador de desarrollo propio e hicimos mucho trabajo de optimización. Los puntos de optimización de la nueva versión del compilador incluyen los siguientes aspectos:

1. Admite que varios compiladores trabajen juntos para desacoplar la construcción de múltiples tipos de proyectos, como el desarrollo de bibliotecas dinámicas.

2. Todo el proceso se almacena en caché durante la fase de compilación, lo que ahorra más del 90% del tiempo de compilación secundaria.

El desarrollo 3.dev utiliza la compilación bajo demanda de forma predeterminada para mejorar el rendimiento de la compilación de una sola página.

4. Admite la compilación multiproceso babel y swc, lo que aumenta la velocidad de compilación completa de 2 a 7 veces.

5. Adopte la nueva versión del protocolo sourcemap, elimine el análisis y la fusión innecesarios y reduzca en gran medida el tiempo dedicado a la fase de paquete.

6. Se ha optimizado el marcado en tiempo de compilación para la compilación de plantillas js, css y swan para reducir el tiempo de fusión de paquetes.

7. Para la compresión y ofuscación de js en las etapas de vista previa y lanzamiento, se adopta una solución paralela de terser y esbuild. Esbuild se utiliza para generar rápidamente paquetes de vista previa y terser puede garantizar la tasa de compresión de los paquetes de lanzamiento.

A juzgar por los resultados, el nuevo compilador ha mejorado significativamente la velocidad, el uso de recursos y la capacidad de mantenimiento en comparación con la versión anterior.

03 Cómo funciona la nueva versión del compilador

El flujo de procesamiento del nuevo compilador es similar al del paquete: el compilador controla el flujo de procesamiento y el procesador realiza la conversión del código. El flujo básico es el siguiente:

Varios módulos importantes:

  • El compilador CompileEntry es el módulo de entrada, que incluye comunicación cli, comunicación del servidor de desarrollo, invocación de comandos, etc.

  • CompileManager es un administrador de compilación, que se utiliza para descargar y administrar recursos dependientes y la creación colaborativa de múltiples compiladores.

  • El compilador es un módulo compilador que se utiliza para compilar el código fuente del proyecto en código de tiempo de ejecución. Puede haber varios compiladores al crear un proyecto.

  • El procesador es un procesador unitario que se utiliza para manejar tareas de compilación individuales, como la conversión y fusión de código.

Nota : Hay 1 compilador para el proyecto de aplicación de mini programa y 2 compiladores para la biblioteca dinámica y los proyectos de extensión dinámica.

3.1 Compilador compilador

Se utiliza para compilar un único proyecto de programa pequeño y compilar el código original del desarrollador en código ejecutable.

deber del trabajo:

1. Cree un contexto de ejecución y proporcione configuración, procesamiento de archivos fs, monitoreo de vigilancia, registrador y otros módulos para uso del Procesador.

2. Compilación completa y segunda compilación cuando se modifican los archivos; la segunda compilación aquí también pasa por el proceso de compilación completo, pero la mayoría utiliza resultados almacenados en caché.

3. Administrar, programar y ejecutar la unidad de procesamiento del Procesador.

4. Mantener las dependencias del procesador y la caché de resultados.

Características:

1. Implemente el almacenamiento en caché de todo el proceso y escriba los parámetros de entrada y los resultados de salida de cada procesador en el caché. Con el almacenamiento en caché, el tiempo de compilación secundaria se puede reducir en un 90%.

2. Admite compilación bajo demanda. Cada compilación de una sola página bajo demanda, compilación incremental y compilación completa siguen el mismo flujo de procesamiento del Procesador.

3. Calcule automáticamente las dependencias de los parámetros de la caché a través del mecanismo Proxy, eliminando la necesidad de generar hashes de caché manualmente para cada procesador, lo que reduce los errores en comparación con el paquete web o el paquete.

4. Solo se mantiene la dependencia del Procesador y no se mantiene ModuleGraph, lo que simplifica el proceso de procesamiento.

Con respecto al almacenamiento en caché de proceso completo, cada empaquetador tiene su propio plan de implementación. El principio básico es generar un hash único para la unidad de procesamiento en función de los parámetros de entrada y dependencias actuales. Si los hashes son consistentes, los resultados serán consistentes.

Dado que el paquete web y el paquete mantienen ModuleGraph, el cálculo y la reutilización del caché serán más complicados. El compilador del subprograma solo realiza cálculos basados ​​en los parámetros de entrada del procesador y las dependencias de llamadas.

3.2 Procesador de la unidad procesadora

El procesador tiene las siguientes características:

1. Cuando los parámetros de entrada son consistentes, se garantiza que la salida será consistente. Tanto la entrada como la salida deben ser serializables en json, logrando un caché completo del procesador.

2. El uri en el procesador es el ID de compilación. Si el ID es consistente durante una sola compilación, los resultados del procesamiento serán consistentes. Por ejemplo, al procesar el archivo app.js, el uri es: js:app.js. La ventaja es que la ruta de procesamiento de recursos del procesador se puede unificar.

3. Los procesadores admiten llamadas entre sí: ProcessWith llama y continúa la ejecución, ProcessWithResult llama y espera el resultado devuelto.

Nota : Los parámetros de entrada aquí incluyen uri, configuración de la aplicación y contextFreeData.

Varios procesadores de uso común:

1.El procesador JS convierte el código es6 en código es5, que es el módulo que consume más tiempo.

2.Swan Processor convierte el código de la plantilla Swan en código js de la capa de vista.

3.Css Processor utiliza postcss para manejar la conversión de unidades, la recopilación de dependencias y otros trabajos en css.

4. El procesador de paquetes fusiona los archivos procesados ​​por el transformador anterior de acuerdo con el algoritmo del paquete y genera los resultados.

Flujo de trabajo del procesador:

El flujo de procesamiento del procesador debe pasar por el proceso de transformación -> paquete. En el mini programa, los paquetes de plantillas js, css y swan se pueden procesar por separado y en paralelo. Esto es diferente del modo de procesamiento del paquete web, y Es similar a la tubería de paquetería.

3.3 Métodos de optimización del rendimiento y del producto.

3.3.1 Optimización de compilación multinúcleo

Dado que la velocidad de inicialización y la eficiencia de la comunicación del módulo multiproceso en Node son mejores que las del multiproceso, la nueva compilación elige utilizar multiproceso para la optimización de múltiples núcleos.

Hay 2 opciones para la compilación multiproceso:

  • Opción 1: programación multiproceso basada en procesadores. Dado que los procesadores admiten llamadas mutuas, el procesamiento real será muy complicado e implicará costos de comunicación.

  • El compilador anterior creó un cargador de subprocesos de trabajo basado en el paquete web y la mejora del rendimiento fue limitada (10% ~ 15%).

  • Parcel es una mejor solución basada en el caché público lmdb para eliminar la comunicación entre subprocesos y garantizar la eficiencia de lectura y escritura.

  • Opción 2: realizar solo la programación multiproceso para la traducción js, con solo dos costos de comunicación.

  • Utilice jest-worker y babel transform para la traducción js multiproceso o utilice swc multiproceso para la traducción js.

Dado que la mayor parte del tiempo de compilación se dedica a la traducción de js (hay muchas dependencias de node_modules en js, todas las cuales deben convertirse), la conversión de módulos css y swan lleva menos tiempo.

La última opción 2 solo realiza traducción js multiproceso. El proceso de procesamiento es simple y los beneficios son mejores. La mejora general es la siguiente:

  • Al utilizar la traducción de babel multiproceso de jest-worker, 4 subprocesos pueden aumentar la velocidad en más de 1 vez.

  • Al usar swc para la traducción js, 4 subprocesos aumentan la velocidad más de 4 veces.

Procesador JS multiproceso:

en:

uri : crea el ID del procesador.

contextFreeData : datos inmutables en una sola compilación, como elementos de configuración en app.json

argumentos de contexto : parámetros globales, como cambio de experimento de optimización, cambio de subprocesos múltiples, etc.

La interfaz de conversión unificada del transformador está estipulada en el procesamiento de conversión js. Según la interfaz, se implementan tres tipos de procesadores: babel de un solo subproceso, babel de múltiples subprocesos y conversión swc, y el cambio de procesador se puede realizar en cualquier momento.

Se pueden realizar configuraciones flexibles para diferentes entornos de compilación:

1. En las herramientas de desarrollo, los desarrolladores pueden cambiar entre los modos de compilación SWC y subprocesos múltiples según la configuración de la máquina para mejorar la eficiencia.

2. La canalización de compilación en la nube permite la compilación multiproceso de forma predeterminada para mejorar el rendimiento.

3. webIDE abre un único hilo de forma predeterminada para reducir el consumo de recursos.

3.3.2 Optimización de la compilación SWC

En comparación con la compilación anterior, el modo de subprocesos múltiples del nuevo compilador se ha mejorado aproximadamente 1 veces. Durante el desarrollo de desarrollo, la primera compilación de algunas páginas de proyectos grandes todavía es un poco lenta y demora más de 10 segundos, principalmente en js. transformar.

swc ahora está básicamente maduro en la traducción js y la mayoría de los escenarios pueden aumentar la velocidad de traducción en más de 4 veces, por lo que se ha agregado soporte de traducción multiproceso de swc y la primera compilación de páginas de proyectos grandes se puede controlar en 5 segundos.

Es necesario escribir dos complementos de swc para adaptarse a la traducción de swc:

  • @swanide/swc-require-rename extraerá la información de la ruta del módulo en require/import/export para facilitar el análisis posterior de las dependencias del módulo en js.

  • @swanide/swc-web-debug realiza procesamiento de instrumentación en código js para admitir la depuración de puntos de interrupción en la depuración de máquinas reales.

La mejora de rendimiento aportada por la compilación SWC es enorme y también se han descubierto algunos problemas durante el uso:

1. Hay una pérdida de memoria en swc. Si la cantidad de compilaciones completas es demasiada durante la etapa de desarrollo, provocará un uso elevado de memoria y el compilador deberá reiniciarse manualmente.

2. El complemento swc admite menos API y algunas funciones que son fáciles de implementar con babel son difíciles de manejar en swc.

3. Dado que swc usa Rust para escribir complementos, los complementos no se pueden usar entre diferentes versiones de @swc/core, y los complementos de swc deben generarse para diferentes plataformas, lo que será más problemático en la implementación.

En el uso real, para algunos escenarios que swc no puede manejar bien, se degradará a babel para su procesamiento.

3.3.3 Compresión de código y almacenamiento en caché en tiempo de ejecución

En la etapa de desarrollo, el código compilado no se comprime y se puede ejecutar en el simulador. Debido a la limitación del tamaño del paquete durante la etapa de lanzamiento de vista previa, se requiere compresión de código para reducir el tamaño del producto.

Hay tres herramientas de compresión de código opcionales:

1.terser tiene una alta tasa de compresión, un pequeño volumen de producto y la velocidad más lenta.

2.swc se comprime rápidamente, el soporte de mangle está incompleto y la tasa de compresión es deficiente.

3.esbuild tiene la compresión más rápida (más de 10 veces más rápida que terser), admite mangle y la tasa de compresión del código no es tan buena como la de terser.

Finalmente, después de comparar y considerar, se seleccionó el siguiente esquema de compresión:

1. Dado que no es necesario un mapa fuente en la fase de vista previa, elimine el mapa fuente y use esbuild para comprimir el código para mejorar la velocidad de vista previa (una gran mejora para escenas de vista previa automática).

2. Utilice terser para la compresión de subprocesos múltiples durante la fase de lanzamiento y conserve el mapa fuente.

El almacenamiento en caché en tiempo de ejecución significa que los resultados intermedios del proceso de compilación se almacenan en caché en la memoria, incluidos los resultados del procesamiento del procesador y los resultados de la compresión del código, lo que puede ahorrar la mayor parte del tiempo de reconstrucción durante la segunda compilación. Dado que las cadenas y los objetos json se retienen en la caché, hay un ahorro de memoria del 40% al 60% en comparación con el compilador antiguo basado en webpack, lo que está dentro de un rango aceptable en términos de uso de memoria.

3.3.4 Optimización del procesamiento de plantillas de cisne

El antiguo procesamiento de plantillas de cisne utiliza swan-loader para la conversión de plantillas. Dado que el alcance de importación de la plantilla no se maneja adecuadamente durante el diseño, la etiqueta <template> y la función de filtro de filtro solo se pueden insertar en el código de la página. Si la plantilla se usa ampliamente en la plantilla y el filtro, el tamaño del código final generado será muy grande.

El nuevo compilador corrige la relación del alcance de la importación, cambia los modos de generación de plantilla y filtro en el producto compilado de en línea a requerir referencia, y luego fusiona el código en la etapa de paquete para que se puedan reutilizar los mismos módulos, lo que llena un gran vacío. .

Nuevo flujo de procesamiento de plantilla de cisne del compilador:

Los posibles productos después de que el procesador procese un único archivo cisne son:

  • módulo de componente de componente, utilizado para generar páginas y componentes personalizados

  • módulo de plantilla

  • función de filtro de filtro, función de filtro sjs

  • código intermedio del documento transformado

Convierta plantillas de cisne en diferentes tipos de módulos js y mantenga las dependencias para facilitar un control más refinado durante la posterior fusión de código.

Por razones históricas, el módulo de plantilla no se puede generar directamente cuando la importación/inclusión contiene sjs o referencias de plantilla. Debe generarse en la plantilla de entrada final. La nueva compilación también proporciona opciones de compilación estática de plantillas, que limitarán estrictamente el alcance de la importación y generarán directamente el código del módulo de plantilla. Para proyectos de programas pequeños generados por taro, puede ahorrar aproximadamente el 30% del tamaño del producto.

3.3.5 Optimización del mapa fuente

Dado que el compilador debe admitir la depuración del código js y el seguimiento de errores en tiempo de ejecución, los mapas fuente deben generarse durante las etapas de desarrollo y lanzamiento.

Al generar código en el paquete web, los mapas fuente deben fusionarse y calcularse. Para proyectos más grandes, la combinación de mapas fuente llevará mucho tiempo y los mapas fuente deben recalcularse cada vez que se vuelven a compilar.

Durante la investigación, descubrí que las herramientas de desarrollo del navegador tienen muy buen soporte para el mapa de índice del protocolo de mapa de origen. El nuevo compilador ha realizado una optimización de fusión de mapa de origen basada en el protocolo de mapa de índice. El cálculo de fusión de mapa de origen de múltiples archivos anterior se ha convertido en un cálculo para generar un mapa de desplazamiento y empalmar el contenido, de esta manera El tiempo necesario para el paquete js ha cambiado de unos pocos segundos a decenas de segundos a un tiempo fijo de menos de 3 segundos.

Lo interesante es que el js-debugger de vscode no admitió la depuración del mapa de índice hasta el 22 de junio (el mapa de índice se lanzó en 2011), y las acciones de Microsoft fueron un poco más lentas.

3.3.6 Trabajo de seguimiento

Al promocionar un nuevo compilador una vez completado el desarrollo, se adopta un método de promoción progresiva:

En la primera etapa , los compiladores nuevos y antiguos de las herramientas de desarrollo coexisten, el desarrollo y la vista previa usan el nuevo compilador y la versión usa el compilador antiguo.

En la segunda etapa , todas las vistas previas y lanzamientos de canalizaciones internas utilizan la nueva compilación.

En la tercera etapa , todas las herramientas de desarrollo se cambian al nuevo compilador.

Todavía hay algunos problemas menores de compatibilidad después de que la nueva versión se compila y se pone en línea. Es necesario exponer los problemas lo antes posible antes de que se pueda lanzar el reemplazo completo.

Para proyectos de programas pequeños, la nueva compilación ha realizado mucho trabajo de optimización y algunos trabajos de optimización aún no se han completado, que incluyen:

Recarga en caliente de HMR : durante el desarrollo, dado que el marco de tiempo de ejecución y las herramientas de desarrollo requieren una adaptación de la interfaz, lleva mucho tiempo depurar para alcanzar las expectativas.

Eliminación del código de agitación de árboles : para los módulos es6, el código de agitación de árboles se puede eliminar durante la etapa de transformación.

elevación del alcance elevación del alcance : teóricamente factible, es necesario verificar el efecto de reducción del código.

Dado que la nueva versión del compilador debe ser totalmente compatible con los resultados de compilación de la versión anterior del compilador, todavía hay espacio para la optimización en el escenario del empaquetado del paquete. Podemos optimizar más los productos empaquetados junto con el tiempo de ejecución. marco en trabajos posteriores.

04 Resumen

La nueva versión del compilador adopta una solución de empaquetado de desarrollo propio. En comparación con el antiguo compilador basado en webpack, ha logrado una enorme mejora en el rendimiento, resolviendo por completo los problemas de compilación lenta y alto uso de recursos. También tiene buenas ventajas de rendimiento sobre los compiladores de los competidores.

Algunos métodos de optimización introducidos por la nueva compilación, como la traducción swc, la compresión esbuild y la optimización del mapa fuente, también se pueden utilizar en la construcción de otros proyectos front-end y tienen un efecto de aceleración.

En el nuevo proyecto compilador, cada estudiante trabajó muy duro y contribuyó con muchas ideas maravillosas, y la mayoría de los problemas encontrados se resolvieron de manera efectiva. Continuaremos adhiriéndose a las dos direcciones de optimización del rendimiento y del producto, y mejoraremos continuamente la experiencia del desarrollador y la eficiencia del tiempo de ejecución.

--FIN--

Lectura recomendada

Práctica de optimización del tamaño del paquete iOS de la aplicación Baidu 50M (6) Método de limpieza inútil

Estrategia de interceptación y distribución de problemas en tiempo real basada en escenarios anormales en línea

Programación de lectura paralela de SSD extremadamente optimizada

La práctica de creación y publicación de textos con IA en la aplicación Baidu

DeeTune: Diseño y aplicación del framework de red Baidu basado en eBPF

Multado con 200 yuanes y más de 1 millón de yuanes confiscados You Yuxi: La importancia de los documentos chinos de alta calidad El servidor de migración de núcleo duro de Musk Solon para JDK 21, ¡los hilos virtuales son increíbles! ! ! El control de congestión de TCP salva Internet Flutter para OpenHarmony está aquí El período LTS del kernel de Linux se restaurará de 6 años a 2 años Go 1.22 solucionará el error de la variable del bucle for Svelte construyó una "nueva rueda" - runas Google celebra su 25 aniversario
{{o.nombre}}
{{m.nombre}}

Supongo que te gusta

Origin my.oschina.net/u/4939618/blog/10114374
Recomendado
Clasificación