¿Cómo lidiar con el problema de (compilar) una gran base de código?

10

Aunque puedo codificar, todavía no tengo experiencia trabajando en proyectos grandes. Lo que hice hasta ahora fue codificar pequeños programas que se compilan en cuestión de segundos (varios ejercicios c / c ++ como algoritmos, principios de programación, ideas, paradigmas, o simplemente probar api ...) o trabajar en algunos proyectos más pequeños que fueron hecho en un lenguaje de scripting (python, php, js) donde no se necesita compilación.

La cuestión es que, cuando codifico en un lenguaje de script, cada vez que quiero probar si algo funciona, simplemente ejecuto el script y veo qué sucede. Si las cosas no funcionan, simplemente puedo cambiar el código e intentarlo nuevamente ejecutando el script nuevamente y seguir haciéndolo hasta que obtenga el resultado que quería. Mi punto es que no tiene que esperar cualquier cosa para compilar y por eso es bastante fácil tomar una gran base de código, modificarla, agregarle algo o simplemente jugar con ella: puede ver los cambios al instante.

Como ejemplo tomaré Wordpress. Es bastante fácil intentar descubrir cómo crear un complemento para él. Primero comienza creando un complemento simple "Hello World", luego crea una interfaz simple para que el panel de administración se familiarice con la API, luego la construye y hace algo más complejo, mientras tanto cambia la apariencia de un par de veces .. La idea de tener que volver a compilar algo tan grande como WP una y otra vez, después de cada cambio menor para intentar "si funciona" y "cómo funciona / siente" simplemente parece ineficiente, lento e incorrecto.

Ahora, ¿cómo podría hacer eso con un proyecto que está escrito en un lenguaje compilado? Me gustaría contribuir a algunos proyectos de código abierto y esta pregunta me sigue molestando. La situación probablemente difiere de un proyecto a otro, donde algunos de ellos que se pensaron sabiamente serán "modulares" de alguna manera, mientras que otros serán solo una gran gota que necesita ser recompilada una y otra vez.

Me gustaría saber más sobre cómo se hace esto correctamente. ¿Cuáles son algunas prácticas comunes, enfoques y diseños de proyectos (patrones) para hacer frente a esto? ¿Cómo se llama esta "modularidad" en el mundo de los programadores y qué debo buscar en Google para obtener más información al respecto? ¿Es frecuente que los proyectos crezcan a partir de sus proporciones de primer pensamiento que se vuelven problemáticas después de un tiempo? ¿Hay alguna manera de evitar la compilación prolongada de proyectos no tan bien diseñados? ¿Una forma de modularizarlos de alguna manera (quizás excluyendo partes no vitales del programa durante el desarrollo (¿alguna otra idea?))?

Gracias.

pootzko
fuente
44
Transmisión exterior. XKCD y la camiseta Thinkgeek relevante * 8 ')
Mark Booth
1
Si trabaja en un proyecto lo suficientemente grande con un presupuesto lo suficientemente grande, puede obtener servidores de compilación para que compilen por usted :)
SoylentGray
@Chad: lo sé, pero solo soy mi máquina de escritorio gnu / linux y yo en este momento :)
pootzko
@Chad Ok, ¿entonces nos estás diciendo que necesitamos servidores dedicados para manejar el volumen de Java (o cualquier otro lenguaje compilado)? Eso es una mierda total
Kolob Canyon
1
@KolobCanyon - No, estoy diciendo que hay una escala en la que podrías trabajar que los requeriría. y que son lo suficientemente baratos ahora que tener una máquina virtual bajo demanda dedicada a la compilación rápida y la automatización de pruebas es tan fácil que la escala no es tan grande.
SoylentGray

Respuestas:

8

Tal como se ha dicho, nunca recompila todo el proyecto cada vez que realiza un pequeño cambio. En cambio, solo recompila la parte del código que ha cambiado, así como todo el código que depende de él.

En C / C ++, la compilación es bastante sencilla. Usted Compilar traducir cada archivo fuente en código máquina (los llamamos objeto archivos * .o) y luego se enlazan todos sus archivos de objetos en una gran ejecutable.

Al igual que MainMa mencionó, algunas bibliotecas están integradas en archivos separados, que se vincularán dinámicamente en tiempo de ejecución con el ejecutable. Estas bibliotecas se denominan Objetos compartidos (* .so) en Unix y Bibliotecas vinculadas dinámicamente (DLL) en Windows. Las bibliotecas dinámicas tienen muchas ventajas, una de las cuales es que no necesita compilarlas / vincularlas, a menos que su código fuente cambie efectivamente.

Existen herramientas de automatización de compilación que lo ayudan a:

  • Especifique dependencias entre diferentes partes de su árbol fuente.
  • Inicie compilaciones puntuales y discretas solo en la parte que se modificó.

Los más famosos (make, ant, maven, ...) pueden detectar automáticamente qué partes del código han cambiado desde la última compilación, y exactamente qué objeto / binario necesita actualizarse.

Sin embargo, esto conlleva el costo (relativamente pequeño) de tener que escribir un "script de compilación". Es un archivo que contiene toda la información sobre su compilación, como definir los objetivos y sus dependencias, definir qué compilador desea y qué opciones usar, definir su entorno de compilación, las rutas de su biblioteca, ... Quizás haya escuchado sobre Makefiles (muy común en el mundo Unix), o build.xml (muy popular en el mundo Java). Esto es lo que ellos hacen.

rahmu
fuente
2
Ant (Java) no puede determinar qué necesita recompilar. Maneja la parte trivial del trabajo, recompilando el código fuente modificado, pero no comprende en absoluto las dependencias de clase. Para eso confiamos en los IDE, y salen mal si se cambia la firma de un método de una manera que no requiera un cambio en el código de llamada.
Kevin Cline
@kevincline Secundo esto: ANT compila todo a menos que especifiques algo diferente en el build.xmlarchivo
Kolob Canyon
7

No recompilas todo el proyecto cada vez. Por ejemplo, si se trata de una aplicación C / C ++, hay posibilidades de que se separe en bibliotecas (DLL en Windows), cada biblioteca se compila por separado.

El proyecto en sí generalmente se compila diariamente en un servidor dedicado: son compilaciones nocturnas. Este proceso puede llevar una gran cantidad de tiempo, ya que incluía no solo el tiempo de compilación, sino también el tiempo dedicado a ejecutar pruebas unitarias, otras pruebas y otros procesos.

Arseni Mourzenko
fuente
3
Si no lo vuelvo a compilar, ¿cuándo tendré tiempo para jugar con mi Trebuchet?
SoylentGray
5

Creo que todas las respuestas hasta ahora se han aludido también, es que los grandes proyectos de software casi siempre se dividen en partes mucho más pequeñas. Cada pieza se almacena normalmente en su propio archivo.

Estas piezas se compilan individualmente para crear objetos. Los objetos son entonces ligados juntos para formar el producto final. [En cierto modo, es como construir cosas con Legos. No intentas moldear la pieza final de una gran pieza de plástico, sino que combinas un montón de piezas más pequeñas para hacerlo.]

Romper el proyecto en piezas que se compilan individualmente permite que sucedan algunas cosas interesantes.

Edificio incremental

En primer lugar, cuando cambia una pieza, generalmente no tiene que volver a compilar todas las piezas. En términos generales, siempre que no cambie la forma en que otras piezas interactúan con su pieza, las otras no necesitan ser recompiladas.

Esto da lugar a la idea de construcción incremental . Cuando se realiza una compilación incremental, solo se recompilan las piezas afectadas por el cambio. Esto acelera enormemente el tiempo de desarrollo. Es cierto que es posible que aún tenga que esperar a que todo se vuelva a vincular, pero que sigue siendo un ahorro por tener que volver a compilar y volver a vincular todo. (Por cierto: algunos sistemas / idiomas admiten enlaces incrementales para que solo las cosas que cambiaron tengan que volver a vincularse. El costo de esto generalmente está en un rendimiento y tamaño de código deficientes).

Examen de la unidad

La segunda cosa que le permite hacer piezas pequeñas es observar individualmente las piezas antes de combinarlas. Esto se conoce como pruebas unitarias . En la Prueba de unidad, cada unidad se prueba individualmente antes de integrarse (combinarse) con el resto del sistema. Las pruebas unitarias se escriben normalmente para que puedan ejecutarse rápidamente sin involucrar al resto del sistema.

El caso limitante de aplicar pruebas se ve en Test Driven Development (TDD). En este modelo de desarrollo, no se escribe / modifica ningún código a menos que sea para corregir una prueba fallida.

Haciéndolo más fácil

Por lo tanto, desglosar las cosas parece bueno, pero también parece que se necesita mucho trabajo para construir el proyecto: debe calcular qué piezas cambiaron y qué depende de esas piezas, compilar cada pieza y luego vincular todo.

Afortunadamente, los programadores son flojos *, por lo que inventan muchas herramientas para facilitar su trabajo. Con ese fin, se han escrito muchas herramientas para automatizar la tarea anterior. Los más famosos de estos ya se han mencionado (make, ant, maven). Estas herramientas le permiten definir qué piezas se deben unir para hacer su proyecto final y cómo las piezas dependen unas de otras (es decir, si cambia esto, es necesario volver a compilarlo). El resultado es que al emitir un solo comando se da cuenta de lo que se necesita volver a compilar, lo compila y se vuelve a vincular todo.

Pero eso todavía deja descubrir cómo se relacionan las cosas entre sí. Eso es mucho trabajo y, como dije antes, los programadores son flojos. Así que se les ocurrió otra clase de herramientas. ¡Estas herramientas se han escrito para determinar las dependencias para usted! A menudo, las herramientas son parte de Entornos de Desarrollo Integrado (IDE) como Eclipse y Visual Studio, pero también hay algunas independientes que se utilizan para aplicaciones genéricas y específicas (makedep, QMake para programas Qt).

* En realidad, los programadores no son realmente vagos, solo les gusta pasar su tiempo trabajando en problemas, no haciendo tareas repetitivas que un programa pueda automatizar.

jwernerny
fuente
5

Aquí está mi lista de cosas que puede intentar para acelerar las compilaciones de C / C ++:

  • ¿Estás configurado para reconstruir solo lo que ha cambiado? La mayoría de los entornos hacen esto por defecto. No es necesario volver a compilar un archivo si éste o ninguno de los encabezados ha cambiado. Del mismo modo, no hay razón para reconstruir un dll / exe si todos los enlaces en objs / lib no han cambiado.
  • Ponga cosas de terceros que nunca cambien y los encabezados asociados en un área de biblioteca de códigos de solo lectura. Solo necesita los encabezados y los binarios asociados. Usted debe nunca se necesita para reconstruir esto desde otra fuente que quizá una vez.
  • Al reconstruir todo, los dos factores limitantes en mi experiencia han sido el número de núcleos y la velocidad del disco . Obtenga una máquina robusta de cuatro núcleos e hiperprocesos con un disco duro realmente bueno y su rendimiento mejorará. Considere una unidad de estado sólido: tenga en cuenta que las baratas pueden ser peores que un buen disco duro. Considera usar raid para aumentar tu disco duro
  • Utilice un sistema de compilación distribuido como Incredibuild que dividirá la compilación en otras estaciones de trabajo en su red. (Asegúrese de tener una red sólida).
  • Configure una compilación de la unidad para evitar recargar constantemente los archivos de encabezado.
Doug T.
fuente
En mi experiencia (no mucho, pero bueno) la velocidad del disco comienza a ser irrelevante si su proyecto va más allá de "muy pequeño". Solo piense en lo que dice en su próximo punto: está utilizando la red para acelerar la compilación. Si el disco fue un gran cuello de botella, recurrir a la red no parece un movimiento muy bueno.
R. Martinho Fernandes
Otra solución barata es compilar en un tmpfs. Puede aumentar considerablemente el rendimiento si el proceso de compilación está vinculado a IO.
Artefact2
4

La idea de tener que volver a compilar algo tan grande como WP una y otra vez, después de cada cambio menor para intentar "si funciona" y "cómo funciona / se siente" simplemente parece ineficiente, lento e incorrecto.

Ejecutar algo interpretado también es muy ineficiente y lento, y (posiblemente) incorrecto. Te estás quejando de los requisitos de tiempo en la PC del desarrollador, pero no compilar causa requisitos de tiempo en la PC del usuario , lo que podría decirse que es mucho peor.

Más importante aún, los sistemas modernos pueden hacer reconstrucciones incrementales bastante avanzadas y no es común recompilar todo para cambios menores: los sistemas compilados pueden incluir componentes de script, especialmente comunes para cosas como la interfaz de usuario.

DeadMG
fuente
1
Creo que mi pregunta no estaba destinada a ser interpretada versus compilar el debate del enfoque. En cambio, solo pedí consejo sobre cómo se realiza correctamente el desarrollo de un proyecto grande (compilado). Sin embargo, gracias por la idea de reconstrucciones incrementales.
pootzko
@pootzko: Bueno, es bastante injusto discutir las desventajas de la compilación cuando no se habla también de las desventajas de la interpretación.
DeadMG
1
no, no es. Es otro debate y no tiene nada que ver con mi pregunta. No digo que sea algo que no deba discutirse. debería, pero no aquí.
pootzko
@pootzko: Entonces no deberías dedicar la mayor parte de tu pregunta a enumerar lo que no te gusta de compilar. Debería haber escrito algo mucho más corto y más breve, como "¿Cómo se pueden reducir los tiempos de compilación de grandes proyectos?".
DeadMG
No sabía que tenía que preguntarle a alguien cómo "debería" hacer mi pregunta ...? : O lo escribí como lo hice para explicar mejor mi punto de vista para que otros pudieran entenderlo mejor y explicarme cómo lograr lo mismo / similar con lenguajes compilados. Nuevamente, no lo hice, le pedí a alguien que me dijera si los idiomas interpretados causan peores requisitos de tiempo en la PC del usuario. Lo sé, y no tiene nada que ver con mi pregunta: "cómo se hace con los lenguajes compilados", lo siento. Otras personas parecen haber dado cuenta de lo que he pedido, sin embargo, así que no creo que mi pregunta no es lo suficientemente claro ..
pootzko
4
  • Reconstrucción parcial

Si el proyecto implementa el DAG de dependencia de compilación adecuado, puede salirse con la recompilación de los archivos de objeto que afecta su cambio.

  • Proceso de compilación múltiple

Suponiendo también un DAG de dependencia de compilación adecuado, puede compilar utilizando múltiples procesos. Un trabajo por núcleo / CPU es la norma.

  • Pruebas ejecutables

Puede crear múltiples ejecutables para pruebas que solo vinculen archivos de objetos particulares.

dietbuddha
fuente
2

Además de la respuesta de MainMa, también hemos actualizado las máquinas en las que trabajamos. Una de las mejores compras que hemos realizado fue un SSD para cuando no puedes evitar recompilar todo el proyecto.

Otra sugerencia sería probar un compilador diferente. En el pasado, cambiamos del compilador de Java a Jikes y ahora pasamos a usar el compilador incluido con Eclipse (no sé si tiene un nombre) que aprovecha mejor los procesadores multinúcleo.

Nuestro proyecto de 37,000 archivos tardó alrededor de 15 minutos en compilarse desde cero antes de realizar estos cambios. Después de los cambios, se redujo a 2-3 minutos.

Por supuesto, vale la pena mencionar el punto de MainMa nuevamente. No vuelva a compilar todo el proyecto cada vez que quiera ver un cambio.

RP.
fuente