¿Por qué la optimización del enlazador es tan pobre?
-
05-07-2019 - |
Pregunta
Recientemente, un compañero de trabajo me señaló que la compilación de todo en un solo archivo creó un código mucho más eficiente que la compilación de archivos de objetos separados, incluso con la optimización de tiempo de enlace activada . Además, el tiempo total de compilación para el proyecto se redujo significativamente. Dado que una de las razones principales para usar C ++ es la eficiencia del código, esto me sorprendió.
Claramente, cuando el archivador / enlazador crea una biblioteca con archivos de objetos, o los vincula en un ejecutable, incluso las optimizaciones simples son penalizadas. En el ejemplo a continuación, los costos de enrolamiento triviales son del 1.8% en el rendimiento cuando lo realiza el enlazador en lugar del compilador. Parece que la tecnología del compilador debería ser lo suficientemente avanzada para manejar situaciones bastante comunes como esta, pero no está sucediendo.
Aquí hay un ejemplo simple usando Visual Studio 2008:
#include <cstdlib>
#include <iostream>
#include <boost/timer.hpp>
using namespace std;
int foo(int x);
int foo2(int x) { return x++; }
int main(int argc, char** argv)
{
boost::timer t;
t.restart();
for (int i=0; i<atoi(argv[1]); i++)
foo (i);
cout << "time : " << t.elapsed() << endl;
t.restart();
for (int i=0; i<atoi(argv[1]); i++)
foo2 (i);
cout << "time : " << t.elapsed() << endl;
}
foo.cpp
int foo (int x) { return x++; }
Resultados de la ejecución: 1.8% de impacto al usar el foo
vinculado en lugar del foo2
.
$ ./release/testlink.exe 100000000
time : 13.375
time : 13.14
Y sí, los indicadores de optimización del vinculador (/ LTCG) están activados.
Solución
No soy un especialista en compiladores, pero creo que el compilador tiene mucha más información disponible para optimizar, ya que opera en un árbol de idiomas, a diferencia del enlazador que tiene que contentarse para operar en la salida de objetos, Mucho menos expresivo que el código que ha visto el compilador. Por lo tanto, el linker y el (los) equipo (s) de desarrollo del compilador (es) dedican menos esfuerzo a realizar optimizaciones de linker que podrían coincidir, en teoría, con los trucos que hace el compilador.
Por cierto, lamento haber distraído tu pregunta original en la discusión sobre ltcg. Ahora entiendo que su pregunta fue un poco diferente, más preocupada por el tiempo de enlace y el tiempo de compilación. Optimizaciones estáticas posibles / disponibles.
Otros consejos
Tu compañero de trabajo está desactualizado. La tecnología está aquí desde 2003 (en el compilador MS C ++): / LTCG . La generación de código de tiempo de enlace está tratando exactamente este problema. Por lo que sé, el GCC tiene esta característica en el radar para el compilador de la próxima generación.
LTCG no solo optimiza el código como las funciones de alineación a través de los módulos, sino que realmente reajusta el código para optimizar la ubicación de la caché y la bifurcación para una carga específica, consulte Optimizaciones guiadas por perfil . Estas opciones generalmente están reservadas solo para compilaciones de lanzamiento, ya que la compilación puede tardar horas en completarse: vinculará un ejecutable instrumentado, ejecutará una carga de perfilado y luego se vinculará nuevamente con los resultados del perfilado. El enlace contiene detalles sobre qué se optimiza exactamente con LTCG:
Inclinación : por ejemplo, si existe Existe una función A que frecuentemente llama a la función B, y la función B es relativamente pequeño, luego guiado por el perfil Las optimizaciones van en línea a la función B en la función A.
Especulación de llamada virtual : si llamada virtual, u otra llamada a través de un puntero de función, frecuentemente se dirige a un Cierta función, un perfil guiado. optimización puede insertar un llamada directa ejecutada condicionalmente a la función frecuentemente dirigida, y la llamada directa puede estar en línea.
Asignación de registro - Optimizando con resultados de datos de perfil en mejor registro de asignación.
Optimización básica de bloques - Bloque básico La optimización permite ejecutar comúnmente Bloques básicos que se ejecutan temporalmente. dentro de un marco dado para ser colocado en El mismo conjunto de páginas (localidad). Esta minimiza el número de páginas utilizadas, minimizando así la sobrecarga de memoria.
Optimización de tamaño / velocidad - Funciones donde el programa pasa mucho tiempo se puede optimizar para la velocidad.
Diseño de la función - Basado en la llamada gráfico y persona que llama / persona llamada Comportamiento, funciones que tienden a ser. a lo largo de la misma ruta de ejecución son colocado en la misma sección.
Optimización de rama condicional - con las sondas de valor, guiadas por perfil optimizaciones pueden encontrar si un determinado Se usa el valor en una instrucción de cambio más a menudo que otros valores. Esta valor puede ser sacado de la cambiar la declaración. Lo mismo se puede hacer. con si / else instrucciones donde el El optimizador puede ordenar el if / else so que el bloque if o else es colocado primero dependiendo de que bloque con más frecuencia es cierto.
Separación de código muerto : código que es no se llama durante el perfilado se mueve a una sección especial que se adjunta Al final del conjunto de secciones. Esto efectivamente mantiene esta sección fuera de las páginas de uso frecuente.
Separación de código EH : el código EH, siendo excepcionalmente ejecutado, puede a menudo se mueven a una sección separada cuando las optimizaciones guiadas por perfil pueden Determinar que se producen las excepciones. solo en condiciones excepcionales.
Intrínsecos de memoria : la expansión de Los intrínsecos se pueden decidir mejor si Se puede determinar si un intrínseco es Llamado con frecuencia. Una lata intrínseca También se optimiza en función del bloque. Tamaño de los movimientos o copias.
Su compañero de trabajo es más inteligente que la mayoría de nosotros. Incluso si parece un enfoque rudo al principio, el proyecto que se inscribe en un solo archivo .cpp tiene una cosa que los otros enfoques, como la optimización del tiempo de enlace, no tienen y no tendrán por un tiempo - fiabilidad
Sin embargo, lo preguntaste hace dos años, y testifico que muchas cosas han cambiado desde entonces (al menos con g ++). La desvirtualización es mucho más confiable, por ejemplo.