c++ - teclas - seleccionar texto no contiguo
¿Por qué no marcar todo en línea? (11)
¡Interesante pregunta! Sin dudas tiene razón en que todas las desventajas enumeradas son específicas del desarrollador. Sugeriría, sin embargo, que un desarrollador desfavorecido tiene muchas menos probabilidades de producir un producto de calidad. Puede que no existan desventajas en tiempo de ejecución, pero imagínese qué tan reacio será un desarrollador a hacer pequeños cambios si cada compilación tarda horas (o incluso días) en completarse.
Me gustaría ver esto desde un ángulo de "optimización prematura": el código modular en múltiples archivos hace la vida más fácil para el programador, por lo que hay un beneficio obvio para hacer las cosas de esta manera. Solo si una aplicación específica resulta ser demasiado lenta y se puede demostrar que al incluir todo mejora de manera medida, incluso consideraría molestar a los desarrolladores. Incluso entonces, sería después de que se haya hecho la mayoría del desarrollo (para poder medirlo) y probablemente solo se haga para las construcciones de producción.
En primer lugar, no estoy buscando una manera de obligar al compilador a alinear la implementación de cada función.
Para reducir el nivel de respuestas equivocadas, asegúrese de comprender lo que significa en realidad la palabra clave en inline
. Aquí hay una buena descripción, inline vs static vs extern .
Entonces mi pregunta, ¿por qué no marca en línea todas las definiciones de funciones? es decir, idealmente, la única unidad de compilación sería main.cpp
. O posiblemente un poco más para las funciones que no se pueden definir en un archivo de encabezado (idioma pimpl, etc.).
La teoría detrás de esta extraña solicitud es que le daría al optimizador la información máxima para trabajar. Podría implementar implementaciones de funciones en línea, por supuesto, pero también podría hacer una optimización de "módulos cruzados" ya que solo hay un módulo. ¿Hay otras ventajas?
¿Alguien ha intentado esto con una aplicación real? ¿Aumentó el rendimiento? ¿¡¿disminución?!?
¿Cuáles son las desventajas de marcar todas las definiciones de funciones en inline
?
- La compilación puede ser más lenta y consumirá mucha más memoria.
- Las versiones iterativas están rotas, toda la aplicación deberá ser reconstruida después de cada cambio.
- Los tiempos de enlace pueden ser astronómicos
Todas estas desventajas solo afectan al desarrollador. ¿Cuáles son las desventajas de tiempo de ejecución?
¿Realmente quisiste decir #include
todo? Eso le daría solo un módulo y le permitiría al optimizador ver todo el programa a la vez.
En realidad, el Visual C ++ de Microsoft hace exactamente esto cuando se usa el /GL
(Whole Program Optimization) , en realidad no compila nada hasta que el enlazador se ejecuta y tiene acceso a todo el código. Otros compiladores tienen opciones similares.
El problema con la creación es que desea que las funciones de alto rendimiento quepan en la memoria caché. Podría pensar que la sobrecarga de llamada a la función es el gran golpe de rendimiento, pero en muchas arquitecturas falla una caché, la pareja empuja y sale del agua. Por ejemplo, si tiene una función grande (tal vez profunda) que necesita ser llamada muy raramente desde su ruta principal de alto rendimiento, podría hacer que su bucle principal de alto rendimiento crezca hasta el punto en que no encaje en L1 icache. Eso ralentizará tu código mucho más que una llamada a una función ocasional.
Esa es prácticamente la filosofía detrás de la optimización de todo el programa y la generación de código de tiempo de enlace (LTCG): las oportunidades de optimización son las mejores con conocimiento global.
Desde un punto de vista práctico, es una especie de dolor porque ahora cada cambio que haga requerirá una recompilación de todo su árbol fuente. En general, necesita una compilación optimizada con menos frecuencia de la que necesita para realizar cambios arbitrarios.
Intenté esto en la era de Metrowerks (es bastante fácil de configurar con una compilación de estilo "Unity") y la compilación nunca finalizó. Lo menciono solo para señalar que es una configuración de flujo de trabajo que probablemente gravará la cadena de herramientas de formas que no estaban anticipando.
Esto está semi relacionado, pero tenga en cuenta que Visual C ++ sí tiene la capacidad de realizar la optimización de módulos cruzados, incluso en línea entre los módulos. Consulte http://msdn.microsoft.com/en-us/library/0zza0de8%28VS.80%29.aspx para obtener información.
Para agregar una respuesta a su pregunta original, no creo que haya un inconveniente en el tiempo de ejecución, suponiendo que el optimizador fuera lo suficientemente inteligente (de ahí que se haya agregado como una opción de optimización en Visual Studio). Simplemente use un compilador lo suficientemente inteligente como para hacerlo automáticamente, sin crear todos los problemas que menciona. :)
La suposición aquí es que el compilador no puede optimizar a través de las funciones. Esa es una limitación de compiladores específicos y no un problema general. Usar esto como una solución general para un problema específico puede ser malo. El compilador puede muy bien simplemente hinchar su programa con lo que podrían haber sido funciones reutilizables en la misma dirección de memoria (llegar a usar el caché) compiladas en otro lugar (y perder rendimiento debido a la caché).
Grandes funciones en el costo general de la optimización, existe un equilibrio entre la sobrecarga de variables locales y la cantidad de código en la función. Mantener el número de variables en la función (tanto aprobadas, locales como globales) dentro del número de variables disponibles para la plataforma hace que la mayoría de los elementos puedan permanecer en los registros y no tengan que ser desalojados para ram, también una pila frame no es necesario (depende del objetivo) por lo que la sobrecarga de llamada de función se reduce notablemente. Es difícil de hacer en aplicaciones del mundo real todo el tiempo, pero la alternativa de un pequeño número de funciones grandes con muchas variables locales, el código va a pasar una cantidad significativa de tiempo desalojando y cargando registros con variables a / desde ram (depende de la objetivo).
Pruebe llvm, puede optimizar en todo el programa no solo función por función. La versión 27 había alcanzado al optimizador de gcc, al menos para una o dos pruebas, no hice pruebas de rendimiento exhaustivas. Y 28 está fuera así que supongo que es mejor. Incluso con algunos archivos, la cantidad de combinaciones de perillas de ajuste es demasiada para meterse. Me parece mejor no optimizar en absoluto hasta que tenga todo el programa en un solo archivo, luego realice su optimización, dando al optimizador todo el programa para trabajar, básicamente, lo que está tratando de hacer con la línea, pero sin el equipaje.
Nosotros (y algunas otras compañías de juegos) lo intentamos haciendo un uber-.CPP que #include
todos los demás; es una técnica conocida. En nuestro caso, no pareció afectar mucho el tiempo de ejecución, pero las desventajas en tiempo de compilación que mencionas resultaron ser completamente paralizantes. Con una compilación de media hora después de cada cambio, se vuelve imposible iterar de manera efectiva. (Y esto es con la aplicación dividida en más de una docena de bibliotecas diferentes).
Intentamos hacer una configuración diferente para tener múltiples .objs mientras depuramos y luego tener el uber-CPP solo en compilaciones de release-opt, pero luego nos encontramos con el problema de que el compilador simplemente se quedó sin memoria. Para una aplicación suficientemente grande, las herramientas simplemente no están listas para compilar un archivo cpp de varias líneas.
También probamos LTCG, y eso proporcionó un pequeño pero agradable impulso en el tiempo de ejecución, en los raros casos en que no se bloqueaba simplemente durante la fase de enlace.
Supongamos que foo()
y bar()
llaman a algún helper()
. Si todo está en una unidad de compilación, el compilador puede optar por no alinear helper()
, para reducir el tamaño total de la instrucción. Esto hace que foo()
haga una llamada de función no en línea a helper()
.
El compilador no sabe que una mejora de un nanosegundo con respecto al tiempo de ejecución de foo()
agrega $ 100 / día a su resultado final a la espera. No sabe que una mejora en el rendimiento o la degradación de algo que no sea foo()
no tiene ningún impacto en su balance final.
Solo usted como programador sabe estas cosas (después de un perfil y análisis cuidadoso, por supuesto). La decisión de no incluir bar()
es una forma de decirle al compilador lo que sabe.
Ya está hecho en algunos casos. Es muy similar a la idea de construcciones unitarias , y las ventajas y desventajas no son fa de lo que descifes:
- más potencial para que el compilador optimice
- El tiempo de enlace básicamente desaparece (si todo está en una sola unidad de traducción, no hay nada para vincular, realmente)
- el tiempo de compilación va, bueno, de una forma u otra. Las construcciones incrementales se vuelven imposibles, como mencionaste. Por otro lado, una compilación completa va a ser más rápida de lo que sería de otra manera (ya que cada línea de código se compila exactamente una vez. En una compilación normal, el código en los encabezados termina siendo compilado en cada unidad de traducción donde se incluye el encabezado). )
Pero en los casos en los que ya tienes un montón de código solo de encabezado (por ejemplo, si usas mucho Boost), podría ser una optimización muy valiosa, tanto en términos de tiempo de compilación como de rendimiento ejecutable.
Como siempre, cuando se trata de rendimiento, depende. No es una mala idea, pero tampoco es universalmente aplicable.
En cuanto al tiempo total, básicamente tienes dos formas de optimizarlo:
- minimice el número de unidades de traducción (para que sus encabezados estén incluidos en menos lugares), o
- minimizar la cantidad de código en los encabezados (de modo que el costo de incluir un encabezado en múltiples unidades de traducción disminuye)
Normalmente, el código C toma la segunda opción, casi al extremo: casi nada aparte de declaraciones directas y macros se mantienen en los encabezados. C ++ a menudo se encuentra en el medio, que es donde se obtiene el peor tiempo de compilación posible (pero PCH y / o compilaciones incrementales pueden reducir el tiempo de nuevo), pero yendo más allá en la otra dirección, minimizando el número de unidades de traducción puede realmente hace maravillas por el tiempo total de construcción.
sqlite usa esta idea. Durante el desarrollo, utiliza una estructura fuente tradicional. Pero para el uso real, hay un gran archivo c (112k líneas). Lo hacen para una optimización máxima. Reclama una mejora del rendimiento del 5-10%
Poco beneficio En un buen compilador para una plataforma moderna, en inline
afectará solo unas pocas funciones. Es solo una pista para el compilador, los compiladores modernos son bastante buenos para tomar esta decisión por sí mismos, y la sobrecarga de una llamada a función se ha reducido (a menudo, el principal beneficio de inline no es reducir la sobrecarga de llamadas, sino abrirse optimizaciones adicionales).
Tiempo de compilación Sin embargo, como en línea también cambia la semántica, tendrá que #include
todo en una unidad de compilación enorme. Esto generalmente aumenta el tiempo de compilación de manera significativa, lo cual es un asesino en proyectos grandes.
Tamaño del código
si se aleja de las plataformas de escritorio actuales y sus compiladores de alto rendimiento, las cosas cambian mucho. En este caso, el aumento del tamaño del código generado por un compilador menos inteligente será un problema, tanto que hace que el código sea significativamente más lento. En las plataformas integradas, el tamaño del código suele ser la primera restricción.
Aún así, algunos proyectos pueden y se benefician de "todo en línea". Le da el mismo efecto que la optimización de tiempo de enlace, al menos si su compilador no sigue ciegamente la inline
.