Compilar un archivo C ++ lleva mucho tiempo en comparación con C # y Java. Se tarda mucho más tiempo en compilar un archivo C ++ que en ejecutar un script Python de tamaño normal. Actualmente estoy usando VC ++ pero es lo mismo con cualquier compilador. ¿Por qué es esto?
Las dos razones por las que podía pensar eran cargar archivos de encabezado y ejecutar el preprocesador, pero eso no parece que deba explicar por qué lleva tanto tiempo.
c++
performance
compilation
Dan Goldstein
fuente
fuente
It takes significantly longer to compile a C++ file
- ¿Quieres decir 2 segundos en comparación con 1 segundo? Ciertamente, eso es el doble de largo, pero apenas significativo. ¿O quieres decir 10 minutos en comparación con 5 segundos? Por favor cuantificar.Respuestas:
Muchas rasones
Archivos de encabezado
Cada unidad de compilación requiere cientos o incluso miles de encabezados para (1) cargar y (2) compilar. Normalmente, cada uno de ellos debe volver a compilarse para cada unidad de compilación, ya que el preprocesador garantiza que el resultado de compilar un encabezado puede variar entre cada unidad de compilación. (Se puede definir una macro en una unidad de compilación que cambia el contenido del encabezado).
Esta es probablemente la razón principal, ya que requiere que se compile una gran cantidad de código para cada unidad de compilación y, además, cada encabezado debe compilarse varias veces (una vez por cada unidad de compilación que lo incluya).
Enlace
Una vez compilados, todos los archivos de objetos deben vincularse entre sí. Este es básicamente un proceso monolítico que no se puede paralelizar muy bien y tiene que procesar todo su proyecto.
Analizando
La sintaxis es extremadamente complicada de analizar, depende en gran medida del contexto y es muy difícil de eliminar. Esto lleva mucho tiempo.
Plantillas
En C #,
List<T>
es el único tipo que se compila, sin importar cuántas instancias de Lista tenga en su programa. En C ++,vector<int>
es un tipo completamente separado devector<float>
, y cada uno tendrá que compilarse por separado.Agregue a esto que las plantillas forman un completo "sub-idioma" completo de Turing que el compilador tiene que interpretar, y esto puede volverse ridículamente complicado. Incluso el código de metaprogramación de plantilla relativamente simple puede definir plantillas recursivas que crean docenas y docenas de instancias de plantillas. Las plantillas también pueden dar como resultado tipos extremadamente complejos, con nombres ridículamente largos, que agregan mucho trabajo adicional al vinculador. (Tiene que comparar muchos nombres de símbolos, y si estos nombres pueden convertirse en muchos miles de caracteres, eso puede llegar a ser bastante costoso).
Y, por supuesto, exacerban los problemas con los archivos de encabezado, porque las plantillas generalmente deben definirse en encabezados, lo que significa que se debe analizar y compilar mucho más código para cada unidad de compilación. En el código C simple, un encabezado generalmente solo contiene declaraciones directas, pero muy poco código real. En C ++, no es raro que casi todo el código resida en archivos de encabezado.
Mejoramiento
C ++ permite algunas optimizaciones muy dramáticas. C # o Java no permiten que las clases se eliminen por completo (tienen que estar allí para fines de reflexión), pero incluso un simple metaprograma de plantilla de C ++ puede generar fácilmente docenas o cientos de clases, todas las cuales están alineadas y eliminadas nuevamente en la optimización fase.
Además, el compilador debe optimizar completamente un programa C ++. El programa AC # puede confiar en el compilador JIT para realizar optimizaciones adicionales en el momento de la carga, C ++ no tiene esas "segundas oportunidades". Lo que genera el compilador está tan optimizado como lo va a ser.
Máquina
C ++ se compila en código máquina que puede ser algo más complicado que el uso de bytecode Java o .NET (especialmente en el caso de x86). (Esto se menciona por completo solo porque se mencionó en comentarios y demás. En la práctica, es poco probable que este paso tome más de una pequeña fracción del tiempo total de compilación).
Conclusión
La mayoría de estos factores son compartidos por el código C, que en realidad se compila de manera bastante eficiente. El paso de análisis es mucho más complicado en C ++ y puede llevar mucho más tiempo, pero el delincuente principal es probablemente las plantillas. Son útiles y hacen que C ++ sea un lenguaje mucho más poderoso, pero también tienen su costo en términos de velocidad de compilación.
fuente
La desaceleración no es necesariamente la misma con ningún compilador.
No he usado Delphi o Kylix, pero en los días de MS-DOS, un programa Turbo Pascal se compilaba casi instantáneamente, mientras que el programa equivalente Turbo C ++ simplemente se arrastraba.
Las dos diferencias principales eran un sistema de módulos muy fuerte y una sintaxis que permitía la compilación de un solo paso.
Ciertamente es posible que la velocidad de compilación no haya sido una prioridad para los desarrolladores de compiladores de C ++, pero también hay algunas complicaciones inherentes en la sintaxis de C / C ++ que dificultan el procesamiento. (No soy un experto en C, pero Walter Bright lo es, y después de construir varios compiladores comerciales de C / C ++, creó el lenguaje D. Uno de sus cambios fue imponer una gramática libre de contexto para facilitar el análisis del lenguaje .)
Además, notará que, en general, los Makefiles están configurados para que cada archivo se compile por separado en C, por lo que si 10 archivos de origen usan el mismo archivo de inclusión, ese archivo de inclusión se procesa 10 veces.
fuente
El análisis y la generación de código son bastante rápidos. El verdadero problema es abrir y cerrar archivos. Recuerde, incluso con incluir guardias, el compilador aún tiene abierto el archivo .H y lee cada línea (y luego la ignora).
Un amigo una vez (mientras estaba aburrido en el trabajo), tomó la solicitud de su empresa y colocó todo, todos los archivos de origen y encabezado, en un archivo grande. El tiempo de compilación se redujo de 3 horas a 7 minutos.
fuente
Otra razón es el uso del preprocesador C para localizar declaraciones. Incluso con los protectores de encabezado, .h aún debe analizarse una y otra vez, cada vez que se incluyen. Algunos compiladores admiten encabezados precompilados que pueden ayudar con esto, pero no siempre se usan.
Ver también: C ++ Respuestas frecuentes
fuente
C ++ se compila en código máquina. Entonces tienes el preprocesador, el compilador, el optimizador y finalmente el ensamblador, todo lo cual tiene que ejecutarse.
Java y C # se compilan en código de bytes / IL, y la máquina virtual Java / .NET Framework se ejecuta (o la compilación JIT en el código de máquina) antes de la ejecución.
Python es un lenguaje interpretado que también se compila en código de bytes.
Estoy seguro de que también hay otras razones para esto, pero en general, no tener que compilar en lenguaje máquina nativo ahorra tiempo.
fuente
Los mayores problemas son:
1) El análisis de encabezado infinito. Ya mencionado. Las mitigaciones (como #pragma una vez) generalmente solo funcionan por unidad de compilación, no por compilación.
2) El hecho de que la cadena de herramientas a menudo se separa en múltiples archivos binarios (make, preprocesador, compilador, ensamblador, archivador, impdef, enlazador y dlltool en casos extremos) que todos tienen que reinicializar y recargar todo el estado todo el tiempo para cada invocación ( compilador, ensamblador) o cada par de archivos (archivador, enlazador y dlltool).
Vea también esta discusión en comp.compilers: http://compilers.iecc.com/comparch/article/03-11-078 especialmente esta:
http://compilers.iecc.com/comparch/article/02-07-128
Tenga en cuenta que John, el moderador de comp.compilers, parece estar de acuerdo, y que esto significa que también debería ser posible lograr velocidades similares para C, si uno integra la cadena de herramientas completamente e implementa encabezados precompilados. Muchos compiladores comerciales de C hacen esto hasta cierto punto.
Tenga en cuenta que el modelo Unix de factorizar todo en un binario separado es una especie de peor modelo para Windows (con su lenta creación de procesos). Es muy notable cuando se comparan los tiempos de compilación de GCC entre Windows y * nix, especialmente si el sistema make / configure también llama a algunos programas solo para obtener información.
fuente
Construyendo C / C ++: lo que realmente sucede y por qué tarda tanto
Una parte relativamente grande del tiempo de desarrollo de software no se gasta en escribir, ejecutar, depurar o incluso diseñar código, sino en esperar a que termine de compilarse. Para acelerar las cosas, primero tenemos que entender lo que sucede cuando se compila el software C / C ++. Los pasos son aproximadamente los siguientes:
Ahora veremos cada paso con más detalle enfocándonos en cómo se pueden hacer más rápido.
Configuración
Este es el primer paso al comenzar a construir. Por lo general, significa ejecutar un script de configuración o CMake, Gyp, SCons o alguna otra herramienta. Esto puede tomar desde un segundo hasta varios minutos para scripts de configuración basados en Autotools muy grandes.
Este paso ocurre relativamente raramente. Solo necesita ejecutarse al cambiar configuraciones o al cambiar la configuración de compilación. A menos que cambien los sistemas de compilación, no hay mucho por hacer para acelerar este paso.
Crear herramienta de inicio
Esto es lo que sucede cuando ejecuta make o hace clic en el icono de construcción en un IDE (que generalmente es un alias para make). El binario de la herramienta de compilación se inicia y lee sus archivos de configuración, así como la configuración de compilación, que generalmente son lo mismo.
Dependiendo de la complejidad y el tamaño de la construcción, esto puede tomar desde una fracción de segundo hasta varios segundos. Por sí solo esto no sería tan malo. Desafortunadamente, la mayoría de los sistemas de compilación basados en make hacen que se invoque decenas a cientos de veces por cada compilación. Por lo general, esto es causado por el uso recursivo de make (que es malo).
Cabe señalar que la razón por la que Make es tan lento no es un error de implementación. La sintaxis de Makefiles tiene algunas peculiaridades que hacen que una implementación realmente rápida sea casi imposible. Este problema es aún más notable cuando se combina con el siguiente paso.
Comprobación de dependencia
Una vez que la herramienta de compilación ha leído su configuración, tiene que determinar qué archivos han cambiado y cuáles deben recompilarse. Los archivos de configuración contienen un gráfico acíclico dirigido que describe las dependencias de compilación. Este gráfico generalmente se construye durante el paso de configuración. El tiempo de inicio de la herramienta de compilación y el escáner de dependencias se ejecutan en cada compilación. Su tiempo de ejecución combinado determina el límite inferior en el ciclo de edición-compilación-depuración. Para proyectos pequeños, este tiempo suele ser de unos segundos. Esto es tolerable Hay alternativas para hacer. El más rápido de ellos es Ninja, que fue construido por ingenieros de Google para Chromium. Si está utilizando CMake o Gyp para construir, simplemente cambie a sus backends Ninja. No tiene que cambiar nada en los archivos de compilación, solo disfrute del aumento de velocidad. Sin embargo, Ninja no está empaquetado en la mayoría de las distribuciones,
Compilacion
En este punto, finalmente invocamos el compilador. Cortando algunas esquinas, aquí están los pasos aproximados tomados.
Contrariamente a la creencia popular, compilar C ++ no es realmente tan lento. El STL es lento y la mayoría de las herramientas de compilación utilizadas para compilar C ++ son lentas. Sin embargo, existen herramientas y formas más rápidas para mitigar las partes lentas del lenguaje.
Usarlos requiere un poco de grasa, pero los beneficios son innegables. Los tiempos de construcción más rápidos conducen a desarrolladores más felices, más agilidad y, finalmente, un mejor código.
fuente
Un lenguaje compilado siempre requerirá una sobrecarga inicial mayor que un lenguaje interpretado. Además, quizás no estructuraste muy bien tu código C ++. Por ejemplo:
Compila mucho más lento que:
fuente
Una manera fácil de reducir el tiempo de compilación en proyectos C ++ más grandes es hacer un archivo de inclusión * .cpp que incluya todos los archivos cpp en su proyecto y compilarlo. Esto reduce el problema de explosión del encabezado a una vez. La ventaja de esto es que los errores de compilación aún harán referencia al archivo correcto.
Por ejemplo, suponga que tiene a.cpp, b.cpp y c.cpp .. cree un archivo: everything.cpp:
Luego compile el proyecto simplemente haciendo todo.cpp
fuente
Algunas razones son:
1) La gramática de C ++ es más compleja que C # o Java y lleva más tiempo analizarla.
2) (Más importante) el compilador de C ++ produce código de máquina y realiza todas las optimizaciones durante la compilación. C # y Java van solo hasta la mitad y dejan estos pasos a JIT.
fuente
La compensación que está obteniendo es que el programa se ejecuta un poco más rápido. Puede ser un consuelo frío para usted durante el desarrollo, pero podría ser muy importante una vez que se complete el desarrollo, y el programa solo lo estén ejecutando los usuarios.
fuente
La mayoría de las respuestas no son claras al mencionar que C # siempre se ejecutará más lentamente debido al costo de realizar acciones que en C ++ se realizan solo una vez en el momento de la compilación, este costo de rendimiento también se ve afectado por las dependencias de tiempo de ejecución (más cosas para cargar para poder para ejecutar), sin mencionar que los programas de C # siempre tendrán una mayor huella de memoria, lo que dará como resultado que el rendimiento esté más estrechamente relacionado con la capacidad del hardware disponible. Lo mismo es cierto para otros idiomas que se interpretan o dependen de una VM.
fuente
Se me ocurren dos problemas que podrían estar afectando la velocidad a la que se compilan sus programas en C ++.
POSIBLE PROBLEMA # 1 - COMPILAR EL ENCABEZADO: (Esto puede o no haber sido abordado por otra respuesta o comentario). Microsoft Visual C ++ (AKA VC ++) admite encabezados precompilados, lo cual recomiendo encarecidamente. Cuando crea un nuevo proyecto y selecciona el tipo de programa que está realizando, debe aparecer una ventana del asistente de configuración en su pantalla. Si presiona el botón "Siguiente>" en la parte inferior, la ventana lo llevará a una página que tiene varias listas de características; asegúrese de que la casilla junto a la opción "Encabezado precompilado" esté marcada. (NOTA: Esta ha sido mi experiencia con las aplicaciones de consola Win32 en C ++, pero este puede no ser el caso con todo tipo de programas en C ++).
POSIBLE PROBLEMA # 2: LA UBICACIÓN SE COMPILA CON: Este verano, tomé un curso de programación, y tuvimos que almacenar todos nuestros proyectos en unidades flash de 8GB, ya que las computadoras en el laboratorio que estábamos usando se borraban todas las noches a la medianoche, lo que habría borrado todo nuestro trabajo. Si está compilando en un dispositivo de almacenamiento externo en aras de la portabilidad / seguridad / etc., puede llevar mucho tiempotiempo (incluso con los encabezados precompilados que mencioné anteriormente) para que su programa se compile, especialmente si es un programa bastante grande. Mi consejo para usted en este caso sería crear y compilar programas en el disco duro de la computadora que está utilizando, y cada vez que quiera / necesite dejar de trabajar en su proyecto (s) por cualquier razón, transfiéralos a su externo dispositivo de almacenamiento, y luego haga clic en el icono "Quitar hardware con seguridad y expulsar medios", que debe aparecer como una pequeña unidad flash detrás de un pequeño círculo verde con una marca de verificación blanca para desconectarlo.
Espero que esto te ayude; déjame saber si lo hace! :)
fuente