2011-02-04 16 views
5

que tiene un segmento de código que es tan simple como:Intel C++ Compiler comprensión de lo que la optimización se realiza

for(int i = 0; i < n; ++i) 
{ 
    if(data[i] > c && data[i] < r) 
    { 
    --data[i]; 
    } 
} 

Es una parte de una función grande y proyecto. Esto es en realidad una reescritura de un bucle diferente, que resultó ser mucho tiempo (bucles largos), pero fue sorprendido por dos cosas:

Cuando los datos [i] fue temporal almacenada como esto:

for(int i = 0; i < n; ++i) 
{ 
    const int tmp = data[i]; 
    if(tmp > c && tmp < r) 
    { 
    --data[i]; 
    } 
} 

Se hizo mucho más lento. No pretendo que esto sea más rápido, pero no puedo entender por qué debería ser mucho más lento, el compilador debería poder determinar si debe usarse o no el tmp.

Pero más importante aún, cuando moví el segmento de código a una función separada, se volvió cuatro veces más lento. Quería entender lo que estaba pasando, así que busqué en el informe opt-in y en ambos casos el loop se vectoriza y parece que hace la misma optimización.

Así que mi pregunta es ¿qué puede hacer una gran diferencia en una función que no se llama un millón de veces, pero consume mucho tiempo en sí misma? ¿Qué buscar en el opt-report?

Podría evitarlo simplemente manteniéndolo en línea, pero el por qué me está molestando.

ACTUALIZACIÓN:

debo subrayar que mi principal preocupación es entender, ¿por qué se hizo más lento, cuando se mueve a una función separada. El ejemplo de código proporcionado con la variable tmp, fue solo un extraño ejemplo que encontré durante el proceso.

+1

Simplemente la pregunta obligatoria: ¿tiene optimizaciones máximas en todas las construcciones? – GManNickG

+2

Intente echar un vistazo al ensamblaje generado. –

+0

Sí, todo está compilado con O3. No he mirado todavía la asamblea, simplemente porque, como dije, es parte de un gran proyecto (también porque no soy un experto en ensamblaje). –

Respuesta

4

Probablemente esté falto de registro y el compilador tenga que cargar y almacenar. Estoy bastante seguro de que las instrucciones de ensamblaje x86 nativas pueden tomar direcciones de memoria para operar, es decir, el compilador puede mantener esos registros libres. Pero al hacerlo local, puede cambiar el comportamiento wrt. aliasing y el compilador puede no ser capaz de demostrar que la versión más rápida tiene la misma semántica, especialmente si hay alguna forma de subprocesos múltiples aquí, lo que le permite cambiar el código.

La función era más lenta cuando en un segmento nuevo probablemente porque las llamadas de función no solo pueden interrumpir la interconexión, sino también crear un pobre rendimiento de la memoria caché (hay código adicional para el parámetro push/pop/etc).

Lección: Deje que el compilador haga la optimización, es más inteligente que usted. No me refiero a eso como un insulto, es más inteligente que yo también. Pero en realidad, especialmente el compilador de Intel, esos tipos saben lo que hacen cuando apuntan a su propia plataforma.

Editar: Más importante aún, debe reconocer que los compiladores están destinados a optimizar el código no optimizado.No están destinados a reconocer código medio optimizado. Específicamente, el compilador tendrá un conjunto de factores desencadenantes para cada optimización, y si escribe el código de manera que no se activen, puede evitar que se realicen las optimizaciones aunque el código sea semánticamente idéntico.

Y también debe considerar el costo de implementación. No todas las funciones ideales para la creación de líneas internas pueden incluirse, simplemente porque la lógica es demasiado compleja para que el compilador la maneje. Sé que VC++ rara vez se alineará con los bucles, incluso si los rendimientos en línea se benefician. Puede estar viendo esto en el compilador de Intel: que los redactores del compilador simplemente decidieron que no valía la pena implementarlo.

Me encontré con esto cuando se trata de bucles en VC++ - el compilador produciría un ensamblaje diferente para dos bucles en formatos ligeramente diferentes, aunque ambos lograron el mismo resultado. Por supuesto, su biblioteca estándar usó el formato ideal. Usted puede observe una aceleración usando std::for_each y un objeto de función.

+0

Estoy de acuerdo con la lección, pero no quería ser más inteligente que el compilador, solo para comprender y aprender a mejorar el código para que se ajuste mejor al compilador. El problema real es comprender por qué se volvió más lento cuando se movió a otro archivo. –

+0

@Bo Jensen: Eso se debe a que muchos compiladores no pueden realizar la alineación en las unidades de traducción debido a la forma en que se diseñó C++. Creo que las versiones recientes de VC++ pueden hacer esto y posiblemente GCC también, pero no creo que Intel pueda hacerlo. – Puppy

+0

gracias por su respuesta. Pero la función está en el mismo archivo. –

1

Tiene razón, el compilador debería poder identificarlo como código no utilizado y eliminarlo/no compilarlo. Eso no significa que realmente lo identifique y lo elimine.

Su mejor opción es mirar el conjunto generado y comprobar para ver exactamente qué está pasando. Recuerde que solo porque un compilador inteligente pueda descifrar cómo hacer una optimización, eso no significa que pueda hacerlo.

Si lo comprueba y ve que el código no se elimina, puede informar al equipo del compilador de inteligencia. Parece que podrían tener un error.

-2

Me sorprende que este

for(int i = 0; i < n; ++i) 
{ 
    const int tmp = data[i]; //?? declaration inside a loop 
    if(tmp > c && tmp < r) 
    { 
    --data[i]; 
    } 
} 

compila en absoluto. Probablemente confunde al compilador. Pruebe

for(int tmp, i = 0; i < n; ++i) 
{ 
    tmp = data[i]; 
    if(tmp > c && tmp < r) 
    { 
    --data[i]; 
    } 
} 

en su lugar. Generalmente use size_t (uint) para repetir el ciclo. Los int firmados se codifican de manera diferente de los no firmados, por lo que puede haber un cambio de bits innecesario. Entonces probaría

int tmp; // well if you must have your temporary, I don't see why you want it, 
     // it costs you 1 register although that should not matter much here. 
for(size_t i = 0; i < n; ++i) 
{ 
    tmp = data[i]; 
    if(tmp > c && tmp < r) 
    { 
    --data[i]; 
    } 
} 

Publique sus resultados.

+0

¿Por qué no debería compilarse? Es un código perfectamente legal. ¿También qué bitshift? Incrementar o disminuir no requiere ningún cambio de bits. –

+0

Es legal pero es inusual. Los índices son unsigned int, por lo que necesita una conversión de 2-complement que es la codificación de int. – supertux

Cuestiones relacionadas