c++ - ejecución - que son los errores en tiempo de compilacion
¿Cuánto afectan las declaraciones a plazo al tiempo de compilación? (5)
Estoy muy interesado en algunos estudios o datos empíricos que muestran una comparación de los tiempos de compilación entre dos proyectos de c ++ que son los mismos, excepto que uno usa declaraciones en la medida de lo posible y el otro no.
¿Qué tan drásticamente pueden las declaraciones de reenvío cambiar el tiempo de compilación en comparación con los totales incluidos?
#include "myClass.h"
contra
class myClass;
¿Hay algún estudio que examine esto?
Me doy cuenta de que esta es una pregunta vaga que depende en gran medida del proyecto. No espero un número difícil para una respuesta. Más bien, espero que alguien pueda dirigirme a un estudio sobre esto.
El proyecto que me preocupa específicamente tiene alrededor de 1200 archivos. Cada cpp en promedio tiene 5 encabezados incluidos. Cada encabezado tiene en promedio 5 encabezados incluidos. Esto hace retroceder unos 4 niveles de profundidad. Parece que para cada cpp compilado, se deben abrir y analizar alrededor de 300 encabezados, algunas veces. (Hay muchos duplicados en el árbol de inclusión). Hay guardias, pero los archivos aún están abiertos. Cada cpp se compila por separado con gcc, por lo que no hay caché de encabezado.
Para asegurarme de que nadie lo malinterprete, ciertamente abogo por el uso de declaraciones a término siempre que sea posible. Mi empleador, sin embargo, los ha prohibido. Estoy tratando de discutir contra esa posición.
Gracias por cualquier información.
Eche un vistazo en el excelente libro de Diseño en C ++ a gran escala de John Lakos: creo que tiene algunas cifras para la declaración a futuro al observar qué sucede si se incluyen N encabezados en los niveles M de profundidad.
Si no usa declaraciones de reenvío, además de aumentar el tiempo total de compilación de un árbol de origen limpio, también aumenta enormemente el tiempo de compilación incremental porque los archivos de encabezado se incluyen innecesariamente. Digamos que tienes 4 clases, A, B, C y D. C usa A y B en su implementación (es decir, en C.cpp
) y D usa C en su implementación. La interfaz de D se ve obligada a incluir Ch debido a esta regla de "no envío de declaraciones". De manera similar, Ch se ve obligado a incluir Ah y Bh, por lo que cada vez que A o B se cambian, D.cpp tiene que reconstruirse aunque no tenga una dependencia directa. A medida que el proyecto se amplía, esto significa que si toca cualquier encabezado tendrá un efecto masivo en la reconstrucción de enormes cantidades de código que simplemente no es necesario.
Tener una regla que no permita la declaración en adelante es (en mi libro) una muy mala práctica. Se va a perder una gran cantidad de tiempo para los desarrolladores sin ningún beneficio. La regla general debería ser que si la interfaz de la clase B depende de la clase A, debe incluir Ah, de lo contrario, debe declararlo. En la práctica, ''depende de'' significa hereda de, utiliza como variable miembro o ''usa cualquier método de''. El lenguaje Pimpl es un método amplio y bien entendido para ocultar la implementación de la interfaz y le permite reducir enormemente la cantidad de reconstrucción necesaria en su base de código.
Si no puede encontrar las cifras de Lakos, le sugeriría que cree sus propios experimentos y tome cronogramas para demostrarle a su gerencia que esta regla está absolutamente equivocada.
Has hecho una pregunta muy general que ha obtenido algunas respuestas generales muy buenas. Pero tu pregunta no era sobre tu problema real:
Para asegurarme de que nadie lo malinterprete, ciertamente abogo por el uso de declaraciones a término siempre que sea posible. Mi empleador, sin embargo, los ha prohibido. Estoy tratando de discutir contra esa posición.
Tenemos alguna información sobre el proyecto, pero no lo suficiente:
El proyecto que me preocupa específicamente tiene alrededor de 1200 archivos. Cada cpp en promedio tiene 5 encabezados incluidos. Cada encabezado tiene en promedio 5 encabezados incluidos. Esto hace retroceder unos 4 niveles de profundidad. Parece que para cada cpp compilado, se deben abrir y analizar alrededor de 300 encabezados, algunas veces. (Hay muchos duplicados en el árbol de inclusión). Hay guardias, pero los archivos aún están abiertos. Cada cpp se compila por separado con gcc, por lo que no hay caché de encabezado.
¿Qué has hecho para usar los encabezados precompilados de gcc? ¿Qué diferencia hace en los tiempos de compilación?
¿Cuánto tiempo se tarda en compilar una compilación limpia ahora? ¿Por cuánto tiempo son las compilaciones típicas (no limpias / incrementales)? Si, como en el ejemplo de James McNellis en los comentarios, los tiempos de compilación son inferiores a un minuto:
El último gran proyecto de C ++ en el que trabajé fue del orden de 1 millón de SLOC (sin incluir bibliotecas de terceros). ... No usamos mucho las declaraciones a plazo y todo se construyó en 10 minutos. Las reconstrucciones incrementales fueron del orden de segundos.
Entonces, realmente no importa cuánto tiempo se ahorraría al evitar incluir: reducir segundos en las compilaciones seguramente no será importante para muchos proyectos.
Toma una pequeña porción representativa de tu proyecto y conviértelo a lo que te gustaría que fuera. Mida las diferencias en el tiempo de compilación entre las versiones no convertidas y convertidas de esa muestra. Recuerda tocar (o el equivalente de crear - asumir-nuevo) varios conjuntos de archivos para representar construcciones reales que encontrarías mientras trabajas.
Muestra a tu empleador cómo serías más productivo.
Las declaraciones adelantadas pueden hacer un código más claro y comprensible que DEBE ser el objetivo de cualquier decisión seguramente.
Combine eso con el hecho de que cuando se trata de clases es posible que dos clases dependan unas de otras, lo que hace que sea un poco difícil NO usar la declaración directa sin causar una pesadilla.
La declaración de clases igualmente reenviada en un encabezado significa que solo necesita incluir los encabezados relevantes en los CPP que realmente UTILIZAN esas clases. Eso en realidad DISMINUYE el tiempo de compilación.
Edición : dado su comentario anterior, me gustaría señalar que SIEMPRE es más lento incluir un archivo de encabezado que el de reenviar declarar. Cada vez que incluye un encabezado, necesita una carga del disco a menudo solo para descubrir que los protectores de encabezado significan que no pasa nada. Eso desperdiciaría una inmensa cantidad de tiempo y es realmente una regla MUY estúpida para traer.
Edición 2 : Los datos duros son bastante difíciles de obtener. Como anécdota, una vez trabajé en un proyecto que no era estricto en cuanto a sus cabeceras y el tiempo de construcción fue de aproximadamente 45 minutos en una memoria RAM de 512MB P3-500Mhz (Esto fue hace un tiempo). Después de pasar 2 semanas reduciendo la pesadilla de inclusión (mediante declaraciones a plazo) logré que el código se incorporara en menos de 4 minutos. Posteriormente, el uso de declaraciones hacia adelante se convirtió en una regla siempre que sea posible.
Edición 3 : también vale la pena tener en cuenta que existe una gran ventaja de usar declaraciones a la hora de realizar pequeñas modificaciones en el código. Si se incluyen encabezados en toda la tienda, una modificación de un archivo de encabezado puede hacer que se reconstruyan grandes cantidades de archivos.
También tomo nota de muchas otras personas que exaltan las virtudes de los encabezados precompilados (PCH). Tienen su lugar y realmente pueden ayudar, pero realmente no deberían usarse como una alternativa a la correcta declaración hacia adelante. De lo contrario, las modificaciones a los archivos de encabezado pueden causar problemas con la compilación de muchos archivos (como se mencionó anteriormente), además de desencadenar una reconstrucción de PCH. Los PCHs pueden proporcionar una gran ganancia para cosas como las bibliotecas que están pre-construidas, pero no son una razón para no usar declaraciones directas apropiadas.
Uhmm, la pregunta es tan clara. Y depende, para ser sencillo.
En un escenario arbitrario, creo que las unidades de traducción no serán más cortas y fáciles de compilar. La intención más respetada de las declaraciones a futuro es proporcionar conveniencia al programador.
#include "myClass.h"
es 1..n líneas
class myClass;
es 1 linea
Ahorrará tiempo a menos que todos sus encabezados sean de 1 trazadores de líneas. Como no hay impacto en la compilación en sí misma (la referencia hacia adelante es solo una manera de decirle al compilador que un símbolo específico se definirá en el momento del enlace, y solo será posible si el compilador no necesita datos de ese símbolo (tamaño de datos, por ejemplo) )), el tiempo de lectura de los archivos incluidos se guardará cada vez que reemplace uno por referencias avanzadas. No existe una medida regular para esto, ya que es un valor por proyecto, pero es una práctica recomendada para proyectos grandes en c ++ (consulte Diseño de software en gran escala en C ++ / John Lakos para obtener más información sobre trucos para administrar proyectos grandes en c ++ incluso si algunos de ellos estan fechados)
Otra forma de limitar el tiempo que pasa el compilador en los encabezados es precompilar los encabezados.