¿Qué técnicas se pueden usar para acelerar los tiempos de compilación de C ++?

249

¿Qué técnicas se pueden usar para acelerar los tiempos de compilación de C ++?

Esta pregunta surgió en algunos comentarios al estilo de programación C ++ de Stack Overflow question , y estoy interesado en saber qué ideas hay.

He visto una pregunta relacionada: ¿Por qué la compilación de C ++ tarda tanto? , pero eso no proporciona muchas soluciones.

Scott Langham
fuente
1
¿Podrías darnos algo de contexto? ¿O estás buscando respuestas muy generales?
Pirolítico
1
Muy similar a esta pregunta: stackoverflow.com/questions/364240/…
Adam Rosenfield
Respuestas generales Tengo una gran base de código escrita por muchas personas. Ideas sobre cómo atacar eso sería bueno. Y también, las sugerencias para mantener compilaciones rápidas para el código recién escrito serían interesantes.
Scott Langham
Tenga en cuenta que a menudo una parte relevante del tiempo de construcción no es utilizado por el compilador, sino por los scripts de construcción
Thi gg
1
Hojeé esta página y no vi ninguna mención de medidas. Escribí un pequeño script de shell que agrega una marca de tiempo a cada línea de entrada que recibe, por lo que puedo canalizar la invocación 'make'. Esto me permite ver qué objetivos son los más caros, el tiempo total de compilación o enlace, etc. simplemente comparando las marcas de tiempo. Si intenta este enfoque, recuerde que las marcas de tiempo serán inexactas para compilaciones paralelas.
John P

Respuestas:

257

Tecnicas de lenguaje

Pimpl Idiom

Echa un vistazo a la idioma Pimpl aquí y aquí , también conocido como puntero opaco o clases de identificador. No solo acelera la compilación, sino que también aumenta la seguridad de las excepciones cuando se combina con unafunción de intercambio sin lanzamiento . El idioma de Pimpl le permite reducir las dependencias entre los encabezados y reduce la cantidad de compilación que debe hacerse.

Declaraciones Reenviadas

Siempre que sea posible, use declaraciones a futuro . Si el compilador solo necesita saber que SomeIdentifieres una estructura o un puntero o lo que sea, no incluya la definición completa, forzando al compilador a hacer más trabajo del que necesita. Esto puede tener un efecto en cascada, lo que hace que sea más lento de lo necesario.

Los flujos de E / S son particularmente conocidos por ralentizar las compilaciones. Si los necesita en un archivo de encabezado, intente #incluir en <iosfwd>lugar de #incluir <iostream>el <iostream>encabezado solo en el archivo de implementación. El <iosfwd>encabezado contiene solo declaraciones hacia adelante. Lamentablemente, los otros encabezados estándar no tienen un encabezado de declaraciones respectivo.

Prefiera pasar por referencia a pasar por valor en las firmas de funciones. Esto eliminará la necesidad de #incluir las definiciones de tipo respectivas en el archivo de encabezado y solo necesitará declarar el tipo hacia adelante. Por supuesto, prefiera las referencias constantes a las referencias no constantes para evitar errores oscuros, pero este es un problema para otra pregunta.

Condiciones de guardia

Use las condiciones de protección para evitar que los archivos de encabezado se incluyan más de una vez en una sola unidad de traducción.

#pragma once
#ifndef filename_h
#define filename_h

// Header declarations / definitions

#endif

Al usar tanto el pragma como el ifndef, obtienes la portabilidad de la solución macro simple, así como la optimización de la velocidad de compilación que algunos compiladores pueden hacer en presencia de la pragma oncedirectiva.

Reduce la interdependencia

Cuanto más modular y menos interdependiente sea el diseño de su código en general, con menos frecuencia tendrá que volver a compilar todo. También puede terminar reduciendo la cantidad de trabajo que el compilador tiene que hacer en cualquier bloque individual al mismo tiempo, en virtud del hecho de que tiene menos para realizar un seguimiento.

Opciones del compilador

Encabezados precompilados

Estos se utilizan para compilar una sección común de encabezados incluidos una vez para muchas unidades de traducción. El compilador lo compila una vez y guarda su estado interno. Ese estado se puede cargar rápidamente para comenzar a compilar otro archivo con ese mismo conjunto de encabezados.

Tenga cuidado de que solo incluya cosas raramente cambiadas en los encabezados precompilados, o podría terminar haciendo reconstrucciones completas con más frecuencia de la necesaria. Este es un buen lugar para los encabezados STL y otras bibliotecas que incluyen archivos.

ccache es otra utilidad que aprovecha las técnicas de almacenamiento en caché para acelerar las cosas.

Usar paralelismo

Muchos compiladores / IDE admiten el uso de múltiples núcleos / CPU para realizar la compilación simultáneamente. En GNU Make (generalmente usado con GCC), use la -j [N]opción. En Visual Studio, hay una opción debajo de las preferencias para permitirle construir múltiples proyectos en paralelo. También puede utilizar la /MPopción de paralelismo a nivel de archivo, en lugar de solo paralelismo a nivel de proyecto.

Otras utilidades paralelas:

Use un nivel de optimización más bajo

Cuanto más intente optimizar el compilador, más difícil será trabajar.

Bibliotecas Compartidas

Mover el código modificado con menos frecuencia a las bibliotecas puede reducir el tiempo de compilación. Al usar bibliotecas compartidas ( .soo .dll), también puede reducir el tiempo de enlace.

Obtenga una computadora más rápida

Más RAM, discos duros más rápidos (incluidas las SSD) y más CPU / núcleos marcarán la diferencia en la velocidad de compilación.

Eclipse
fuente
11
Sin embargo, los encabezados precompilados no son perfectos. Un efecto secundario de usarlos es que se incluyen más archivos de los necesarios (porque cada unidad de compilación usa el mismo encabezado precompilado), lo que puede forzar recompilaciones completas con más frecuencia de la necesaria. Sólo algo para tener en cuenta.
jalf
8
En los compiladores modernos, #ifndef es tan rápido como #pragma una vez (siempre que el protector de inclusión esté en la parte superior del archivo). Así que no hay beneficio para #pragma una vez en términos de velocidad de compilación
jalf
77
Incluso si solo tiene VS 2005, no 2008, puede agregar un interruptor / MP en las opciones de compilación para habilitar la construcción paralela en el nivel .cpp.
macbirdie
66
Los SSD eran prohibitivamente caros cuando se escribió esta respuesta, pero hoy son la mejor opción al compilar C ++. Accede a muchos archivos pequeños al compilar ;. Eso requiere una gran cantidad de IOPS, que los SSD entregan.
MSalters
14
Prefiera pasar por referencia a pasar por valor en las firmas de funciones. Esto eliminará la necesidad de # incluir las definiciones de tipo respectivas en el archivo de encabezado Esto está mal , no necesita tener el tipo completo para declarar una función que pasa por valor, solo necesita el tipo completo para implementar o usar esa función , pero en la mayoría de los casos (a menos que solo esté reenviando llamadas) necesitará esa definición de todos modos.
David Rodríguez - dribeas
43

Trabajo en el proyecto STAPL, que es una biblioteca de C ++ con muchas plantillas. De vez en cuando, tenemos que volver a visitar todas las técnicas para reducir el tiempo de compilación. Aquí, he resumido las técnicas que usamos. Algunas de estas técnicas ya se enumeran arriba:

Encontrar las secciones que requieren más tiempo

Aunque no existe una correlación probada entre las longitudes de los símbolos y el tiempo de compilación, hemos observado que los tamaños promedio de símbolos más pequeños pueden mejorar el tiempo de compilación en todos los compiladores. Entonces, su primer objetivo es encontrar los símbolos más grandes en su código.

Método 1: ordena los símbolos según el tamaño

Puede usar el nmcomando para enumerar los símbolos en función de sus tamaños:

nm --print-size --size-sort --radix=d YOUR_BINARY

En este comando, le --radix=dpermite ver los tamaños en números decimales (el valor predeterminado es hexadecimal). Ahora, mirando el símbolo más grande, identifique si puede romper la clase correspondiente e intente rediseñarla factorizando las partes sin plantilla en una clase base, o dividiendo la clase en varias clases.

Método 2: ordena los símbolos según la longitud

Puede ejecutar el nmcomando regular y canalizarlo a su script favorito ( AWK , Python , etc.) para ordenar los símbolos en función de su longitud . Según nuestra experiencia, este método identifica los mayores problemas para hacer que los candidatos sean mejores que el método 1.

Método 3 - Use Templight

" Templight es un Clang herramienta basada en para perfilar el consumo de tiempo y memoria de las instancias de plantillas y para realizar sesiones de depuración interactivas para ganar introspección en el proceso de creación de instancias de plantillas".

Puede instalar Templight consultando LLVM y Clang ( instrucciones ) y aplicando el parche Templight en él. La configuración predeterminada para LLVM y Clang está en depuración y afirmaciones, y esto puede afectar significativamente el tiempo de compilación. Parece que Templight necesita ambos, por lo que debe usar la configuración predeterminada. El proceso de instalación de LLVM y Clang debería llevar aproximadamente una hora.

Después de aplicar el parche, puede usarlo templight++ubicado en la carpeta de compilación que especificó durante la instalación para compilar su código.

Asegúrate de que templight++esté en tu RUTA. Ahora para compilar agregue los siguientes modificadores a su CXXFLAGSen su Makefile o a sus opciones de línea de comando:

CXXFLAGS+=-Xtemplight -profiler -Xtemplight -memory -Xtemplight -ignore-system

O

templight++ -Xtemplight -profiler -Xtemplight -memory -Xtemplight -ignore-system

Una vez realizada la compilación, tendrá un .trace.memory.pbf y .trace.pbf generados en la misma carpeta. Para visualizar estos rastros, puede usar las Herramientas de Templight que pueden convertirlos a otros formatos. Siga estas instrucciones para instalar templight-convert. Usualmente usamos la salida callgrind. También puede usar la salida GraphViz si su proyecto es pequeño:

$ templight-convert --format callgrind YOUR_BINARY --output YOUR_BINARY.trace

$ templight-convert --format graphviz YOUR_BINARY --output YOUR_BINARY.dot

El archivo callgrind generado se puede abrir usando kcachegrind en el que puede rastrear la instanciación que consume más tiempo / memoria.

Reducir el número de instancias de plantilla

Aunque no hay una solución exacta para reducir el número de instancias de plantillas, existen algunas pautas que pueden ayudar:

Clases de refactorización con más de un argumento de plantilla

Por ejemplo, si tienes una clase,

template <typename T, typename U>
struct foo { };

y los dos Ty Upuede tener 10 opciones diferentes, que han aumentado las posibles instancias de la plantilla de esta clase a 100. Una forma de resolver este es abstraer la parte común del código para una clase diferente. El otro método es utilizar la inversión de herencia (revertir la jerarquía de clases), pero asegúrese de que sus objetivos de diseño no se vean comprometidos antes de utilizar esta técnica.

Refactorice el código sin plantilla para unidades de traducción individuales

Con esta técnica, puede compilar la sección común una vez y vincularla con sus otras TU (unidades de traducción) más adelante.

Use instancias de plantilla externas (desde C ++ 11)

Si conoce todas las posibles instancias de una clase, puede usar esta técnica para compilar todos los casos en una unidad de traducción diferente.

Por ejemplo, en:

enum class PossibleChoices = {Option1, Option2, Option3}

template <PossibleChoices pc>
struct foo { };

Sabemos que esta clase puede tener tres posibles instancias:

template class foo<PossibleChoices::Option1>;
template class foo<PossibleChoices::Option2>;
template class foo<PossibleChoices::Option3>;

Ponga lo anterior en una unidad de traducción y use la palabra clave externa en su archivo de encabezado, debajo de la definición de clase:

extern template class foo<PossibleChoices::Option1>;
extern template class foo<PossibleChoices::Option2>;
extern template class foo<PossibleChoices::Option3>;

Esta técnica puede ahorrarle tiempo si está compilando diferentes pruebas con un conjunto común de instancias.

NOTA: MPICH2 ignora la instanciación explícita en este punto y siempre compila las clases instanciadas en todas las unidades de compilación.

Usa construcciones de unidad

La idea detrás de las compilaciones de la unidad es incluir todos los archivos .cc que usa en un archivo y compilar ese archivo solo una vez. Con este método, puede evitar la reinstalación de secciones comunes de diferentes archivos y si su proyecto incluye muchos archivos comunes, probablemente también ahorraría en accesos a disco.

A modo de ejemplo, supongamos que tiene tres archivos foo1.cc, foo2.cc, foo3.ccy todos ellos incluyen tupledesde STL . Puede crear uno foo-all.ccque se vea así:

#include "foo1.cc"
#include "foo2.cc"
#include "foo3.cc"

Compila este archivo solo una vez y potencialmente reduce las instancias comunes entre los tres archivos. En general, es difícil predecir si la mejora puede ser significativa o no. Pero un hecho evidente es que perderías paralelismo en sus compilaciones (ya no puede compilar los tres archivos al mismo tiempo).

Además, si alguno de estos archivos ocupa mucha memoria, es posible que se quede sin memoria antes de que termine la compilación. En algunos compiladores, como GCC , esto podría ICE (Error interno del compilador) su compilador por falta de memoria. Así que no use esta técnica a menos que conozca todos los pros y los contras.

Encabezados precompilados

Los encabezados precompilados (PCH) pueden ahorrarle mucho tiempo en la compilación compilando sus archivos de encabezado en una representación intermedia reconocible por un compilador. Para generar archivos de encabezado precompilados, solo necesita compilar su archivo de encabezado con su comando de compilación habitual. Por ejemplo, en GCC:

$ g++ YOUR_HEADER.hpp

Esto generará un YOUR_HEADER.hpp.gch file( .gches la extensión para archivos PCH en GCC) en la misma carpeta. Esto significa que si incluye YOUR_HEADER.hppen algún otro archivo, el compilador usará su en YOUR_HEADER.hpp.gchlugar de YOUR_HEADER.hppen la misma carpeta antes.

Hay dos problemas con esta técnica:

  1. Debe asegurarse de que los archivos de encabezado que se precompilan son estables y no van a cambiar ( siempre puede cambiar su archivo MAKE )
  2. Solo puede incluir una PCH por unidad de compilación (en la mayoría de los compiladores). Esto significa que si tiene que precompilar más de un archivo de encabezado, debe incluirlos en un archivo (por ejemplo, all-my-headers.hpp). Pero eso significa que debe incluir el nuevo archivo en todos los lugares. Afortunadamente, GCC tiene una solución para este problema. Use -includey dele el nuevo archivo de encabezado. Puede separar por coma diferentes archivos con esta técnica.

Por ejemplo:

g++ foo.cc -include all-my-headers.hpp

Use espacios de nombres anónimos o sin nombre

Los espacios de nombres sin nombre (también conocidos como espacios de nombres anónimos) pueden reducir significativamente los tamaños binarios generados. Los espacios de nombres sin nombre utilizan enlaces internos, lo que significa que los símbolos generados en esos espacios de nombres no serán visibles para otras TU (unidades de traducción o compilación). Los compiladores generalmente generan nombres únicos para espacios de nombres sin nombre. Esto significa que si tiene un archivo foo.hpp:

namespace {

template <typename T>
struct foo { };
} // Anonymous namespace
using A = foo<int>;

Y resulta que incluye este archivo en dos TU (dos archivos .cc y los compila por separado). Las dos instancias de plantilla foo no serán las mismas. Esto viola la Regla de una definición (ODR). Por la misma razón, se desaconseja el uso de espacios de nombres sin nombre en los archivos de encabezado. Siéntase libre de usarlos en sus .ccarchivos para evitar que aparezcan símbolos en sus archivos binarios. En algunos casos, cambiar todos los detalles internos de un .ccarchivo mostró una reducción del 10% en los tamaños binarios generados.

Cambiar las opciones de visibilidad

En los compiladores más nuevos, puede seleccionar sus símbolos para que sean visibles o invisibles en los Objetos dinámicos compartidos (DSO). Idealmente, cambiar la visibilidad puede mejorar el rendimiento del compilador, las optimizaciones de tiempo de enlace (LTO) y los tamaños binarios generados. Si observa los archivos de encabezado STL en GCC, puede ver que se usa ampliamente. Para habilitar las opciones de visibilidad, debe cambiar su código por función, por clase, por variable y, lo que es más importante, por compilador.

Con la ayuda de la visibilidad, puede ocultar los símbolos que considera privados de los objetos compartidos generados. En GCC puede controlar la visibilidad de los símbolos pasando por defecto u oculto a la -visibilityopción de su compilador. Esto es, en cierto sentido, similar al espacio de nombres sin nombre, pero de una manera más elaborada e intrusiva.

Si desea especificar las visibilidades por caso, debe agregar los siguientes atributos a sus funciones, variables y clases:

__attribute__((visibility("default"))) void  foo1() { }
__attribute__((visibility("hidden")))  void  foo2() { }
__attribute__((visibility("hidden")))  class foo3   { };
void foo4() { }

La visibilidad predeterminada en GCC es predeterminada (pública), lo que significa que si compila lo anterior como un -sharedmétodo de biblioteca compartida ( ), foo2y la clase foo3no será visible en otras TU ( foo1y foo4será visible). Si compila con -visibility=hidden, solo foo1será visible. Incluso foo4estaría oculto.

Puede leer más sobre la visibilidad en el wiki de GCC .

Mani Zandifar
fuente
33

Recomiendo estos artículos de "Juegos desde dentro, diseño y programación de juegos independientes":

De acuerdo, son bastante viejos: tendrás que volver a probar todo con las últimas versiones (o versiones disponibles) para obtener resultados realistas. De cualquier manera, es una buena fuente de ideas.

Pablo
fuente
17

Una técnica que funcionó bastante bien para mí en el pasado: no compile varios archivos fuente C ++ de forma independiente, sino que genere un archivo C ++ que incluya todos los demás archivos, como este:

// myproject_all.cpp
// Automatically generated file - don't edit this by hand!
#include "main.cpp"
#include "mainwindow.cpp"
#include "filterdialog.cpp"
#include "database.cpp"

Por supuesto, esto significa que debe volver a compilar todo el código fuente incluido en caso de que cambie cualquiera de las fuentes, por lo que el árbol de dependencia empeora. Sin embargo, compilar múltiples archivos fuente como una unidad de traducción es más rápido (al menos en mis experimentos con MSVC y GCC) y genera binarios más pequeños. También sospecho que el compilador tiene más potencial para optimizaciones (ya que puede ver más código a la vez).

Esta técnica se rompe en varios casos; por ejemplo, el compilador rescatará en caso de que dos o más archivos fuente declaren una función global con el mismo nombre. Sin embargo, no pude encontrar esta técnica descrita en ninguna de las otras respuestas, por eso la menciono aquí.

Para lo que vale, el Proyecto KDE utilizó exactamente esta misma técnica desde 1999 para construir binarios optimizados (posiblemente para un lanzamiento). Se llamó al cambio al script de configuración de compilación --enable-final. Por interés arqueológico, desenterré la publicación que anunciaba esta característica: http://lists.kde.org/?l=kde-devel&m=92722836009368&w=2

Frerich Raabe
fuente
2
No estoy seguro de si es realmente lo mismo, pero supongo que activar "Optimización de todo el programa" en VC ++ ( msdn.microsoft.com/en-us/library/0zza0de8%28VS.71%29.aspx ) debería tener mismo efecto sobre el rendimiento en tiempo de ejecución que lo que sugiere. ¡Sin embargo, el tiempo de compilación definitivamente puede ser mejor en su enfoque!
Philipp
1
@Frerich: Estás describiendo las compilaciones de Unity mencionadas en la respuesta de OJ. También los he visto llamados compilaciones masivas y compilaciones maestras.
idbrii
Entonces, ¿cómo se compara un UB con WPO / LTCG?
Paul
Esto es potencialmente útil solo para compilaciones de una sola vez, no durante el desarrollo donde se alterna entre edición, construcción y prueba. En el mundo moderno, la norma es cuatro núcleos, tal vez un par de años después el recuento de núcleos es significativamente mayor. Si el compilador y el enlazador no pueden utilizar varios subprocesos, entonces la lista de archivos tal vez podría dividirse en <core-count> + Nsublistas que se compilan en paralelo donde Nhay algún número entero adecuado (dependiendo de la memoria del sistema y cómo se usa la máquina).
FooF
15

Hay un libro completo sobre este tema, que se titula Diseño de software C ++ a gran escala (escrito por John Lakos).

Las plantillas anteriores al libro, por lo que al contenido de ese libro agregue "el uso de plantillas también puede hacer que el compilador sea más lento".

ChrisW
fuente
A menudo se hace referencia al libro en este tipo de temas, pero para mí era escaso en información. Básicamente establece usar declaraciones hacia adelante tanto como sea posible y desacoplar dependencias. Eso indica un poco lo obvio además de que usar el modismo de pimpl tiene inconvenientes en el tiempo de ejecución.
gast128
@ gast128 Creo que su objetivo es utilizar modismos de codificación que permitan una compilación incremental, es decir, de modo que si cambia un poco de fuente en algún lugar no tendrá que volver a compilar todo.
ChrisW
15

Simplemente vincularé a mi otra respuesta: ¿Cómo se reduce el tiempo de compilación y el tiempo de vinculación para proyectos de Visual C ++ (C ++ nativo)? . Otro punto que quiero agregar, pero que a menudo causa problemas es usar encabezados precompilados. Pero, por favor, úselos solo para partes que casi nunca cambian (como los encabezados del kit de herramientas GUI). De lo contrario, al final te costarán más tiempo del que te salvan.

Otra opción es, cuando trabaja con GNU make, activar la -j<N>opción:

  -j [N], --jobs[=N]          Allow N jobs at once; infinite jobs with no arg.

Usualmente lo tengo 3puesto que tengo un doble núcleo aquí. Luego ejecutará compiladores en paralelo para diferentes unidades de traducción, siempre que no existan dependencias entre ellos. La vinculación no se puede hacer en paralelo, ya que solo hay un proceso de vinculación que vincula todos los archivos de objetos.

Pero el enlazador en sí puede ser enhebrado, y esto es lo que hace el enlazador ELF . Es un código C ++ roscado optimizado que se dice que vincula los archivos de objetos ELF una magnitud más rápida que la anterior (y que en realidad se incluyó en binutils ).GNU gold ld

Johannes Schaub - litb
fuente
OK sí. Lo siento, esa pregunta no surgió cuando busqué.
Scott Langham
No tenías que disculparte. eso fue para Visual C ++. Su pregunta parece ser para cualquier compilador. así que está bien :)
Johannes Schaub - litb
12

Aquí están algunas:

  • Utilice todos los núcleos de procesador iniciando un trabajo de compilación múltiple ( make -j2es un buen ejemplo).
  • Desactive o disminuya las optimizaciones (por ejemplo, GCC es mucho más rápido con -O1que -O2o -O3).
  • Utilice encabezados precompilados .
Milan Babuškov
fuente
12
Para su información, creo que generalmente es más rápido iniciar más procesos que núcleos. Por ejemplo, en un sistema de cuatro núcleos, normalmente uso -j8, no -j4. La razón de esto es que cuando un proceso está bloqueado en E / S, el otro puede estar compilando.
Sr. Fooz
@MrFooz: Probé esto hace unos años compilando el kernel de Linux (desde el almacenamiento RAM) en un i7-2700k (4 núcleos, 8 hilos, configuré un multiplicador constante). Olvidé el mejor resultado exacto, pero -j12en general -j18fueron mucho más rápidos que -j8, tal como sugieres. Me pregunto cuántos núcleos puede tener antes de que el ancho de banda de la memoria se convierta en el factor limitante ...
Mark K Cowan
@ MarkKCowan depende de muchos factores. Las diferentes computadoras tienen anchos de banda de memoria muy diferentes. Con los procesadores de alta gama en estos días, se necesitan múltiples núcleos para saturar el bus de memoria. Además, existe el equilibrio entre E / S y CPU. Algunos códigos son muy fáciles de compilar, otros códigos pueden ser lentos (por ejemplo, con muchas plantillas). Mi regla general actual es -jcon 2 veces el número de núcleos reales.
Sr. Fooz
11

Una vez que haya aplicado todos los trucos de código anteriores (declaraciones directas , reduciendo la inclusión de encabezados al mínimo en los encabezados públicos, introduciendo la mayoría de los detalles dentro del archivo de implementación con Pimpl ...) y nada más se puede obtener en términos de lenguaje, considere su sistema de compilación . Si usa Linux, considere usar distcc (compilador distribuido) y ccache (compilador de caché).

El primero, distcc, ejecuta el paso del preprocesador localmente y luego envía la salida al primer compilador disponible en la red. Requiere las mismas versiones de compilador y biblioteca en todos los nodos configurados en la red.

El último, ccache, es un caché del compilador. Nuevamente ejecuta el preprocesador y luego verifica con una base de datos interna (guardada en un directorio local) si ese archivo del preprocesador ya ha sido compilado con los mismos parámetros del compilador. Si lo hace, solo aparece el binario y la salida de la primera ejecución del compilador.

Ambos pueden usarse al mismo tiempo, de modo que si ccache no tiene una copia local, puede enviarlo a través de la red a otro nodo con distcc, o simplemente puede inyectar la solución sin más procesamiento.

David Rodríguez - dribeas
fuente
2
No creo que distcc requiera las mismas versiones de biblioteca en todos los nodos configurados. distcc solo realiza la compilación de forma remota, no la vinculación. También envía el código preprocesado a través del cable, por lo que los encabezados disponibles en el sistema remoto no importan.
Frerich Raabe
9

Cuando salí de la universidad, el primer código C ++ digno de producción real que vi tenía estas directivas arcanas #ifndef ... #endif entre ellas donde se definieron los encabezados. Le pregunté al tipo que estaba escribiendo el código sobre estas cosas generales de una manera muy ingenua y me presentaron al mundo de la programación a gran escala.

Volviendo al punto, el uso de directivas para evitar definiciones de encabezado duplicadas fue lo primero que aprendí a la hora de reducir los tiempos de compilación.

questzen
fuente
1
viejo pero dorado. a veces lo obvio se olvida.
alcor
1
'incluir guardias'
gast128
8

Más RAM

Alguien habló sobre unidades de RAM en otra respuesta. Hice esto con un 80286 y Turbo C ++ (muestra la edad) y los resultados fueron fenomenales. Al igual que la pérdida de datos cuando la máquina se bloqueó.

señor calendario
fuente
en el DOS no se puede tener tanta memoria aunque
phuclv
6

Use declaraciones hacia adelante donde pueda. Si una declaración de clase solo usa un puntero o una referencia a un tipo, puede simplemente declararla hacia adelante e incluir el encabezado para el tipo en el archivo de implementación.

Por ejemplo:

// T.h
class Class2; // Forward declaration

class T {
public:
    void doSomething(Class2 &c2);
private:
    Class2 *m_Class2Ptr;
};

// T.cpp
#include "Class2.h"
void Class2::doSomething(Class2 &c2) {
    // Whatever you want here
}

Menos incluye significa mucho menos trabajo para el preprocesador si lo hace lo suficiente.

Evan Teran
fuente
¿No importa esto solo cuando se incluye el mismo encabezado en varias unidades de traducción? Si solo hay una unidad de traducción (como suele ser el caso cuando se usan plantillas), esto parece no tener ningún impacto.
Siempre aprendiendo
1
Si solo hay una unidad de traducción, ¿por qué molestarse en ponerla en un encabezado? ¿No tendría más sentido poner los contenidos en el archivo fuente? ¿No es el punto principal de los encabezados que es probable que sea incluido por más de un archivo fuente?
Evan Teran
5

Utilizar

#pragma once

en la parte superior de los archivos de encabezado, por lo que si se incluyen más de una vez en una unidad de traducción, el texto del encabezado solo se incluirá y analizará una vez.

Scott Langham
fuente
2
Aunque es ampliamente compatible, #pragma una vez no es estándar. Ver en.wikipedia.org/wiki/Pragma_once
ChrisInEdmonton
77
Y en estos días, los guardias regulares incluyen el mismo efecto. Como siempre y cuando estén en la parte superior del archivo, el compilador es totalmente capaz de tratarlos como pragma una vez
JALF
5

Solo para completar: una compilación puede ser lenta porque el sistema de compilación es estúpido y porque el compilador está tardando mucho en hacer su trabajo.

Lea Recursive Make Considered Damful (PDF) para una discusión de este tema en entornos Unix.

dmckee --- gatito ex moderador
fuente
4
  • Actualiza tu computadora

    1. Obtenga un quad core (o un sistema dual-quad)
    2. Obtenga MUCHA RAM.
    3. Use una unidad de RAM para reducir drásticamente los retrasos de E / S de archivos. (Hay empresas que fabrican unidades de RAM IDE y SATA que actúan como discos duros).
  • Entonces tienes todas tus otras sugerencias típicas

    1. Utilice encabezados precompilados si están disponibles.
    2. Reduzca la cantidad de acoplamiento entre las partes de su proyecto. Cambiar un archivo de encabezado generalmente no debería requerir volver a compilar todo el proyecto.
Uhall
fuente
4

Tuve una idea sobre el uso de una unidad de RAM . Resultó que para mis proyectos no hace mucha diferencia después de todo. Pero entonces son bastante pequeños todavía. ¡Intentalo! Me interesaría saber cuánto ayudó.

Vilx-
fuente
Huh ¿Por qué alguien votó en contra de esto? Lo intentaré mañana.
Scott Langham
1
Espero que el voto negativo sea porque nunca hace una gran diferencia. Si tiene suficiente RAM sin usar, el sistema operativo la usará de manera inteligente como caché de disco de todos modos.
MSalters
1
@MSalters: ¿y cuánto sería "suficiente"? Yo sé que esa es la teoría, pero por alguna razón el uso de un RAMdrive no realmente dar un impulso significativo. Vaya figura ...
Vilx-
1
suficiente para compilar su proyecto y aún almacenar en caché la entrada y los archivos temporales. Obviamente, el lado en GB dependerá directamente del tamaño de su proyecto. Cabe señalar que en los sistemas operativos más antiguos (WinXP en particular), los cachés de archivos eran bastante vagos, dejando la RAM sin usar.
MSalters
¿Seguramente la memoria RAM es más rápida si los archivos ya están en RAM en lugar de hacer un montón de IO lento primero, y luego están en RAM? (subida-repetición para archivos que han cambiado; escríbalos de nuevo en el disco, etc.).
Paul
3

El enlace dinámico (.so) puede ser mucho más rápido que el enlace estático (.a). Especialmente cuando tienes una unidad de red lenta. Esto se debe a que tiene todo el código en el archivo .a que debe procesarse y escribirse. Además, un archivo ejecutable mucho más grande debe escribirse en el disco.


fuente
la vinculación dinámica a prevenir muchos tipos de optimizaciones en tiempo de enlace de forma que la salida puede ser más lenta en muchos casos
phuclv
3

No se trata del tiempo de compilación, sino del tiempo de compilación:

  • Use ccache si tiene que reconstruir los mismos archivos cuando está trabajando en sus archivos de compilación

  • Usa ninja-build en lugar de make. Actualmente estoy compilando un proyecto con ~ 100 archivos fuente y todo está almacenado en caché por ccache. necesita 5 minutos, ninja menos de 1.

Puede generar sus archivos ninja de cmake con -GNinja.

thi gg
fuente
3

¿Dónde pasas tu tiempo? ¿Estás vinculado a la CPU? ¿Atado a la memoria? Disco atado? ¿Puedes usar más núcleos? Más RAM? ¿Necesitas RAID? ¿Simplemente desea mejorar la eficiencia de su sistema actual?

Bajo gcc / g ++, ¿has mirado a ccache ? Puede ser útil si está haciendo make clean; makemucho.

Mr.Ree
fuente
2

Discos duros más rápidos.

Los compiladores escriben muchos (y posiblemente enormes) archivos en el disco. Trabaje con SSD en lugar del disco duro típico y los tiempos de compilación son mucho más bajos.

linello
fuente
2

En Linux (y tal vez otros * NIX), realmente puede acelerar la compilación NO ESTRELLANDO en la salida y cambiando a otro TTY.

Aquí está el experimento: printf ralentiza mi programa

Flavio
fuente
2

Los recursos compartidos de redes ralentizarán drásticamente su compilación, ya que la latencia de búsqueda es alta. Para algo como Boost, marcó una gran diferencia para mí, a pesar de que nuestro disco compartido de red es bastante rápido. El tiempo para compilar un programa Boost de juguete pasó de aproximadamente 1 minuto a 1 segundo cuando cambié de un recurso compartido de red a un SSD local.

Mark Lakata
fuente
2

Si tiene un procesador multinúcleo, tanto Visual Studio (2005 y posterior) como GCC admiten compilaciones multiprocesador. Es algo que debes habilitar si tienes el hardware, seguro.

Peter Mortensen
fuente
2
@Fellman, vea algunas de las otras respuestas: use la opción -j #.
extraño
1

Aunque no es una "técnica", no pude entender cómo los proyectos Win32 con muchos archivos fuente se compilaron más rápido que mi proyecto vacío "Hello World". Por lo tanto, espero que esto ayude a alguien como a mí.

En Visual Studio, una opción para aumentar los tiempos de compilación es la vinculación incremental ( / INCREMENTAL ). Es incompatible con la generación de código de tiempo de enlace ( / LTCG ), por lo tanto, recuerde deshabilitar el enlace incremental al hacer versiones de lanzamiento.

Nathan Goings
fuente
1
deshabilitar Link-time Code Generation no es una buena sugerencia ya que deshabilita muchas optimizaciones. /INCREMENTAL
Debe
1

A partir de Visual Studio 2017, tiene la capacidad de tener algunas métricas del compilador sobre lo que lleva tiempo.

Agregue esos parámetros a C / C ++ -> Línea de comando (Opciones adicionales) en la ventana de propiedades del proyecto: /Bt+ /d2cgsummary /d1reportTime

Puedes tener más información en esta publicación .

Xavier Bigand
fuente
0

El uso de enlaces dinámicos en lugar de estáticos te hace compilar más rápido de lo que puedes sentir.

Si usa t Cmake, active la propiedad:

set(BUILD_SHARED_LIBS ON)

Build Release, el uso de enlaces estáticos puede ser más optimizado.

Cheng
fuente