What the Fork - herramienta de visualización de builds en tiempo real para C/C++/Rust
(danielchasehooper.com)- What the Fork es una herramienta multiplataforma que visualiza en tiempo real distintos procesos de build, como los de C/C++/Rust
- Permite identificar fácilmente problemas estructurales de los sistemas de build existentes, como falta de paralelismo y procesos ineficientes
- Funciona con todos los sistemas de build y lenguajes de programación, con soporte para diversas herramientas de build como make, ninja, gradle, zig y cargo
- Mediante monitoreo de llamadas al sistema, visualiza en forma de cajas el tiempo de ejecución, los comandos y las dependencias de cada proceso
- Es una herramienta muy útil para optimizar builds, analizar cuellos de botella y mejorar el rendimiento de CI
Introducción y contexto
- What the Fork es una herramienta de visualización de builds en tiempo real desarrollada para diagnosticar visualmente por qué un build se vuelve lento
- En proyectos como LLVM, la compilación puede ser lenta simplemente por el gran volumen de código, pero en la mayoría de los casos los builds tardan más de lo necesario debido a configuraciones ineficientes
- Hasta ahora era difícil revisar directamente los problemas de un build o ver de un vistazo los problemas estructurales, por eso hacía falta una herramienta así
- Esta herramienta fue diseñada para ser multiplataforma y se puede aplicar a cualquier sistema de build y lenguaje
Funciones principales y uso
- What the Fork no es un simple profiler del sistema, sino una herramienta enfocada en diagnosticar problemas específicos de builds
- Algunos ejemplos son detectar el no uso de la bandera
-jcon make, concentración de tiempo en un archivo o etapa de compilación concreta, y comandos que se ejecutan en secuencia aunque podrían procesarse en paralelo - Es especialmente eficaz para analizar y optimizar el rendimiento de clean builds en entornos de CI
- Se usa anteponiendo el comando
wtfal comando de build (por ejemplo:wtf make,wtf cargo build,wtf npm run build) - Cuando empieza el build, se abre la UI y actualiza en tiempo real el progreso de cada proceso
UI y forma de visualización
- Cada proceso de build se muestra como una caja en una línea de tiempo y se diferencia por color según su tipo
- La relación padre-hijo entre procesos se representa con una estructura anidada
- En el panel inferior se muestran el tiempo de ejecución, el directorio de trabajo y los argumentos completos del comando del proceso seleccionado
Cómo funciona
- Un build es una combinación de varios procesos (por ejemplo:
bash,clang,ld) - Los builds de gran escala usan distintas herramientas como
cargo,make,bazel,gradleyxcodebuild, que en la práctica ejecutan muchos comandos, dependencias, cachés y tareas de scheduling - Solo con la salida de la terminal no es posible entender la estructura detallada de tiempos ni los procesos anidados (por ejemplo,
ldinvocado internamente porclang) - Para resolver esto, aprovecha llamadas al sistema que detectan el inicio y fin de procesos según el sistema operativo (macOS: Endpoint Security API, Linux: ptrace(), Windows: Event Tracing for Windows)
- De esta manera, puede reconstruir todo el proceso de build y su línea de tiempo, e identificar la ruta de ejecución y el tiempo de cada etapa
- También puede usarse para rastrear distintos subprocesos más allá de los builds
Casos reales y observaciones
- Varios ingenieros (de Delta, Mozilla y Apple) lo aplicaron en proyectos reales y encontraron problemas inesperados
- Ejemplo 1: en un proyecto open source que usa Cargo, se confirmó falta de paralelismo porque los archivos se compilaban secuencialmente (en una CPU de 10 núcleos se observó potencial para una mejora de más de 10 veces)
- Ejemplo 2: en un build de LLVM con Ninja, todos los núcleos de CPU trabajaban en paralelo de forma eficiente, logrando una eficiencia de build ideal
- Ejemplo 3: en un proyecto basado en CMake, se detectó una estructura ineficiente con ejecuciones anidadas de cmake/make/clang y 85 repeticiones de verificación de versión de Xcode/OS, donde el trabajo real era mínimo
- Ejemplo 4: en un gran proyecto Objective-C con xcodebuild, se observó falta de paralelismo en la fase final del build y un período inactivo de 6 segundos antes de comenzar; en comparación, ninja empezaba a compilar casi de inmediato, tras 0.4 segundos
- Ejemplo 5: al compilar Orca Project con Zig, el orden de build de las dependencias se determinaba de forma aleatoria, por lo que la eficiencia del paralelismo cambiaba según la suerte. Se observó que algunas dependencias se ejecutaban al final y reducían el paralelismo
- Ejemplo 6: en el proyecto GitHub CLI con make/go, el tiempo de descarga de dependencias era elevado. Reducir las dependencias podría mejorar la velocidad del build
Efectos de uso y limitaciones
- El análisis visual de la línea de tiempo permite identificar cuellos de botella inesperados, repeticiones innecesarias de dependencias y áreas con poco paralelismo
- Permite detectar rápidamente oportunidades de mejora estructural, como problemas de dependencias, retrabajo innecesario o ineficiencias de herramientas concretas, y aplicarlas directamente a la optimización del rendimiento del build
- Ver el comando completo de cada proceso permite un análisis más detallado
Programa beta
- What the Fork funciona en Windows, Linux y macOS
- Las personas y equipos que quieran dar feedback pueden solicitar acceso a la beta privada (se proporciona un enlace a Google Forms)
3 comentarios
El comando está demasiado chistoso jajaja... o sea,
wtf...Ojalá lo publiquen como código abierto.
Comentarios en Hacker News
En este momento estoy atrapado en un entorno donde solo se pueden usar CMake, GCC y Unix Make, así que es casi imposible obtener información detallada de por qué el build es lento; es un build caótico con pasos complejos como copiar archivos del código fuente al directorio de build, varios lenguajes (C, C++, Fortran, Python), pasos personalizados de CMake, etc. Si esta herramienta funciona bien incluso en un entorno tan desordenado, creo que se podría aprender muchísimo.
tsoding escribió una librería C de un solo header para builds multiplataforma en https://github.com/tsoding/nob.h; solo necesitas
cc. Se pueden inspeccionar los pasos del build usando una herramienta de perfilado de GDB. Me parece una gran idea; quizá no le sirva al autor de este post, pero si tienes que lidiar con varios lenguajes, Nix es una excelente herramienta de build.Yo mismo hice una herramienta de tracing/perfilado del tiempo de compilación como plugin de GCC; por si a alguien le interesa: https://github.com/royjacobson/externis
Cuando intentaba reducir el tiempo de compilación de mi motor de juegos, usaba el tamaño de los artefactos compilados como una métrica sustituta. Como el tiempo de reloj era demasiado inestable, resultaba más fácil trabajar con una medición del tamaño del binario, que daba el mismo resultado entre builds e incluso en distintas máquinas. No coincidía al 100%, pero en la práctica sí ayudaba.
Tengo un problema parecido. A veces he visto que CMake recompila hasta archivos que ni siquiera toqué; por ejemplo, aunque solo cambie un poco un
.cppsin modificar interfaces, termina recompilando objetos completamente independientes. A veces me pregunto si CMake no estará creando dependencias más fuertes de lo que realmente existe entre los archivos, y por eso el tiempo de build se alarga sin necesidad.Le sugeriría al autor del blog que muestre en la parte superior de la página, justo debajo del encabezado, un GIF del build de la app de macOS. Queda bien mostrar primero el resultado que hiciste y luego poner la explicación.
Me encanta este proyecto. Hice algo parecido en 2018 usando strace y dtruss, y https://buildinfer.loopperfect.com/, para cosas como generar archivos BUCK automáticamente. Lo visualizaba con graphviz, perfetto.dev, etc. Me da pena no haberlo empaquetado como producto formal, pero en consultoría ayudó muchísimo para diagnosticar causas y migrar a BUCK/Bazel. Últimamente he vuelto a revisarlo pensando en aplicarlo más ampliamente. También hay desafíos técnicos esenciales en este enfoque: si los logs de system calls se escriben al disco, crecen hasta decenas o cientos de GB (por ejemplo, llvm llega a 50 GB y en algunos casos pasa de 100 GB); además hay que manejar bien también pasos de build como https, IPC, etc. (una vez un cliente incluso traía código desde una base de datos Firebird con Perl cada vez que hacía un build); y por ser análisis en tiempo de ejecución, también hay que repetir el análisis para cada configuración de build.
LD_PRELOADo filtrado con eBPF.Me parece genial. Siento que hay muchísimos problemas que se nos escapan por no tener una visualización así. Hace 10 años, cuando optimizaba el sistema de build de Mozilla, una herramienta así me habría ayudado muchísimo. En el artículo me habría gustado ver más sobre qué problemas reales encontraron.
En un proyecto de C++ gestionado con CMake, me fue bien usando ninjatracing y
-ftime-tracede Clang para visualizar el rendimiento del build. Además, con ClangBuildAnalyzer se puede analizar con más detalle en qué está gastando tiempo el compilador.Me parece realmente genial. ¿Hay planes de abrir el código? Estoy haciendo algo parecido y me gustaría colaborar.
Si usas el compilador Visual C++ en Windows, también recomiendo vcperf. Ya viene incluido por defecto en VS2022 o se puede compilar directamente desde GitHub. También lo he usado con proyectos generados por UBT o CMake. No recuerdo si permite evaluar directamente la calidad de la paralelización del build, pero sí deja ver fácilmente información del frontend del compilador, sobre todo para encontrar headers que se incluyen con mucha frecuencia o que son intrínsecamente pesados.
Aunque suele pasarse por alto, una observación importante es que la lógica de build “baked in” (precalculada) del sistema de build en realidad no rastrea con granularidad qué cambios afectan a qué. Por ejemplo, en ninja parte de la lógica de build ya viene preingresada, y por eso es rápido. Yo hice benchmarks de full build de Xerces-C++ con ninja (configurado con CMake) y con build2 (una herramienta que maneja en el build tanto la configuración como el seguimiento de cambios): con ninja fueron 3.23 s y con build2 3.54 s. Si se repite el build conservando algunos de los archivos que genera CMake, baja a 3.28 s. Como referencia, solo la etapa de configuración de CMake tarda 4.83 s. Un build CMake+ninja de stack completo realmente toma unos 8 s, que suele ser el tiempo que experimentas cuando usas esto como librería.
He probado algo parecido ejecutando Instruments durante el build para ver qué proceso hace qué y cuándo. La desventaja es que si el build tarda mucho, Instruments se pone lento. También es incómodo que no permita filtrar por árbol de procesos, pero me ayudó muchísimo cuando reduje de forma importante el tiempo de build del código iOS de Twitter. Últimamente el tracing de "All Processes" de Instruments se rompió, así que este método ya no se puede usar.
Se ve muy bueno. ¿Existe ya una versión para macOS que se pueda probar ahora mismo? Me gustaría usarla también para trabajo en Rust o C++/Swift.
No tengo pensado distribuir una versión para macOS al siguiente usuario beta de prueba después de corregir algunos bugs; si te registras (al final del artículo) y mencionas este comentario, seguro te incluyo en el grupo beta.
Todavía no parece haber una versión pública para ningún sistema operativo; solo se puede pedir acceso anticipado.