2010-07-17 18 views
33

Durante la optimización del programa, tratando de optimizar un bucle que itera a través de un vector, encontré el siguiente hecho: :: std :: vector :: at() es EXTREMADAMENTE más lento que el operador []!¿Por qué std :: vector :: operator [] 5 a 10 veces más rápido que std :: vector :: at()?

El operador [] es de 5 a 10 veces más rápido que en(), tanto en la liberación de & versiones de depuración (x86 VS2008).

Al leer un poco en la web me di cuenta de que en() tiene una comprobación de límites. Ok, pero, ralentizando la operación hasta 10 veces?

¿Hay alguna razón para eso? Quiero decir, la verificación de límites es una simple comparación numérica, ¿o me falta algo?
La pregunta es, ¿cuál es el verdadero motivo de este golpe de rendimiento?
Además, ¿hay alguna manera de hacerlo aún más rápido?

Sin duda voy a intercambiar todas mis llamadas at() con [] en otras partes del código (en las que ya tengo una verificación de límite personalizada).

Prueba de concepto:

#define _WIN32_WINNT 0x0400 
#define WIN32_LEAN_AND_MEAN 
#include <windows.h> 

#include <conio.h> 

#include <vector> 

#define ELEMENTS_IN_VECTOR 1000000 

int main() 
{ 
    __int64 freq, start, end, diff_Result; 
    if(!::QueryPerformanceFrequency((LARGE_INTEGER*)&freq)) 
     throw "Not supported!"; 
    freq /= 1000000; // microseconds! 

    ::std::vector<int> vec; 
    vec.reserve(ELEMENTS_IN_VECTOR); 
    for(int i = 0; i < ELEMENTS_IN_VECTOR; i++) 
     vec.push_back(i); 

    int xyz = 0; 

    printf("Press any key to start!"); 
    _getch(); 
    printf(" Running speed test..\n"); 

    { // at() 
     ::QueryPerformanceCounter((LARGE_INTEGER*)&start); 
     for(int i = 0; i < ELEMENTS_IN_VECTOR; i++) 
      xyz += vec.at(i); 
     ::QueryPerformanceCounter((LARGE_INTEGER*)&end); 
     diff_Result = (end - start)/freq; 
    } 
    printf("Result\t\t: %u\n\n", diff_Result); 

    printf("Press any key to start!"); 
    _getch(); 
    printf(" Running speed test..\n"); 

    { // operator [] 
     ::QueryPerformanceCounter((LARGE_INTEGER*)&start); 
     for(int i = 0; i < ELEMENTS_IN_VECTOR; i++) 
      xyz -= vec[i]; 
     ::QueryPerformanceCounter((LARGE_INTEGER*)&end); 
     diff_Result = (end - start)/freq; 
    } 

    printf("Result\t\t: %u\n", diff_Result); 
    _getch(); 
    return xyz; 
} 

Editar:
Ahora se está assiged el valor a "xyz", por lo que el compilador no "borrar" a cabo.

+0

Tal vez en realidad se debe hacer algo con los elementos en lugar de sólo les pedía o el compilador podría optimizar la basura. – schnaader

+0

No te entendí. ¿Explique? – Poni

+1

Intenta hacer algo como 'test_int + = vec [i]' en los bucles for. Como no está haciendo nada con el elemento vectorial, el compilador podría optimizarlo completamente. También vea la respuesta de Ben para esto. – schnaader

Respuesta

51

La razón es que un acceso no verificado probablemente se puede hacer con instrucciones de un solo procesador. Un acceso verificado también tendrá que cargar el tamaño de la memoria, compararlo con el índice y (suponiendo que esté dentro del rango) saltar una rama condicional al manejador de errores. Puede haber más problemas para manejar la posibilidad de lanzar una excepción. Esto será muchas veces más lento, y esta es precisamente la razón por la que tiene ambas opciones.

Si puede probar que el índice está dentro del rango sin un control de tiempo de ejecución, utilice operator[]. De lo contrario, use at(), o agregue su propio cheque antes de acceder. operator[] debe ser más o menos lo más rápido posible, pero explotará desordenadamente si el índice no es válido.

+25

Bueno, explotará si tienes suerte. :) –

3

No se hace nada con el valor de retorno, por lo que si el compilador especifica estas funciones, puede optimizarlas completamente. O quizás pueda optimizar completamente la versión del subíndice ([]). Correr sin optimizaciones es inútil desde la perspectiva de la medición del rendimiento, lo que necesita es un programa simple pero útil para ejercer las funciones para que no solo se optimicen. Por ejemplo, podría mezclar el vector (intercambie aleatoriamente 50000 pares de elementos).

25

que corrió su código de prueba en mi máquina:

En una versión de depuración sin optimizar, la diferencia entre los dos bucles es insignificante.

En una compilación de versión optimizada, el segundo ciclo for está optimizado por completo (la llamada a operator[] está probablemente en línea y el optimizador puede ver que el ciclo no hace nada y puede eliminar todo el ciclo).

Si cambio el cuerpo de los bucles para realizar un trabajo real, por ejemplo, vec.at(i)++; y vec[i]++;, respectivamente, la diferencia entre los dos bucles es insignificante.

No veo esta diferencia de rendimiento de cinco a diez veces que usted ve.

+0

Está ejecutando la compilación de depuración con la depuración de iterador habilitada. –

+0

@Hans: Mi prueba fue con la configuración predeterminada, por lo que la depuración del iterador se habilitó en la compilación de depuración (sin embargo, creo que este es el resultado esperado, que funcionarían aproximadamente igual) ya que habilita verificaciones vinculadas para 'op [ ] '). Si deshabilito la depuración de iteradores, le da a 'op []' una ganancia de rendimiento de aproximadamente dos sobre 'at()'. (Cuando publiqué la respuesta originalmente, no me estaba enfocando realmente en el rendimiento de compilación de depuración, pero tiene razón: la depuración de los iteradores puede marcar una gran diferencia en una compilación de depuración). –