Los proyectos de software grandes generalmente están compuestos de muchas unidades de compilación que pueden compilarse de manera relativamente independiente, por lo que la compilación a menudo se paraliza en una granularidad muy aproximada invocando el compilador varias veces en paralelo. Esto sucede a nivel de los procesos del sistema operativo y es coordinado por el sistema de compilación en lugar del compilador apropiado. Me doy cuenta de que esto no es lo que pidió, pero eso es lo más parecido a la paralelización en la mayoría de los compiladores.
¿Por qué es eso? Bueno, gran parte del trabajo que hacen los compiladores no se presta fácilmente a la paralelización:
- No puedes simplemente dividir la entrada en varios fragmentos y leerlos de forma independiente. Por simplicidad, querría dividirse en los límites de lexme (para que ningún hilo comience en medio de un lexme), pero determinar los límites de lexme potencialmente requiere mucho contexto. Por ejemplo, cuando saltas en la mitad del archivo, debes asegurarte de no saltar en una cadena literal. Pero para comprobar esto, tienes que mirar básicamente a todos los personajes que vinieron antes, que es casi tanto trabajo como simplemente decirlo para empezar. Además, el lexing rara vez es el cuello de botella en los compiladores para los idiomas modernos.
- El análisis es incluso más difícil de paralelizar. Todos los problemas de dividir el texto de entrada para el lexing se aplican aún más a la división de los tokens para el análisis, por ejemplo, determinar dónde comienza una función es básicamente tan difícil como analizar el contenido de la función para empezar. Si bien también puede haber formas de evitar esto, probablemente serán desproporcionadamente complejas para el pequeño beneficio. El análisis tampoco es el mayor cuello de botella.
- Después de haber analizado, normalmente es necesario realizar una resolución de nombres, pero esto conduce a una enorme red de relaciones entrelazadas. Para resolver una llamada de método aquí, es posible que primero tenga que resolver las importaciones en este módulo, pero eso requiere resolver los nombres en la unidad de compilación otra , etc. Lo mismo para la inferencia de tipo si su idioma tiene eso.
Después de esto, se vuelve un poco más fácil. La comprobación de tipos y la optimización y la generación de código podrían, en principio, estar paralelizadas en la granularidad de la función. Todavía sé de pocos compiladores, si es que hay alguno, que hacen esto, tal vez porque hacer una tarea tan grande al mismo tiempo es bastante difícil. También debe considerar que la mayoría de los proyectos de software grandes contienen tantas unidades de compilación que el enfoque de "ejecutar un montón de compiladores en paralelo" es completamente suficiente para mantener todos sus núcleos ocupados (y en algunos casos, incluso una granja de servidores completa). Además, en grandes tareas de compilación, la E / S del disco puede ser un cuello de botella tan grande como el trabajo real de compilación.
Dicho todo esto, sé de un compilador que paraliza el trabajo de generación y optimización de código. El compilador Rust puede dividir el trabajo de back-end (LLVM, que en realidad incluye optimizaciones de código que tradicionalmente se consideran "middle-end") entre varios subprocesos. Esto se llama "unidades de código-gen". En contraste con las otras posibilidades de paralelización discutidas anteriormente, esto es económico porque:
- El lenguaje tiene unidades de compilación bastante grandes (en comparación con, por ejemplo, C o Java), por lo que podría haber menos unidades de compilación en vuelo de las que tiene núcleos.
- La parte que se está paralizando usualmente toma la mayor parte del tiempo de compilación.
- El trabajo de back-end es, en su mayor parte, vergonzosamente paralelo: simplemente optimice y traduzca al código de máquina cada función de forma independiente. Por supuesto, hay optimizaciones entre procedimientos, y las unidades de código sí las obstaculizan y, por lo tanto, afectan el rendimiento, pero no hay problemas semánticos.