viernes, 2 de septiembre de 2011

Math Engine : Capitulo 5. LPVector con SSE(I).

Capítulo perteneciente al math engine LordPakus

Hola a todos....

Aquí os dejo la implementación de la suma de vectores mediante SSE.

int main (int argc, char* argv[])
{
    LPVector v1,v2,v3;
    clock_t timer;
    float delay;

    //Creamos los vectores aleatorios
    v1.random(DIM,0.0f,500.0f);
    v2.random(DIM,0.0f,500.0f);
    v3 = v1;

    //Sumamos los vectores
    cout << "Version sin optimizar\n";
    timer = clock();
    v1.addOld ( v2 );
    delay = ( clock() - timer ) ;
    cout << "Tiempo gastado: " << delay << "milisegundos\n";

    cout << "Version optimizada con SSE\n";
    timer = clock();
    v3.addSSE ( v2 );
    delay = ( clock() - timer ) ;
    cout << "Tiempo gastado: " << delay << "milisegundos\n";

    if ( v1 != v3)
        cout << "Error: La operacion da diferente resultado!!!\n";

    if ( v1 == v3)
        cout << "Calculo correcto\n";

    //Eliminamos los vectores
    v1.Delete();
    v2.Delete();
    v3.Delete();

    system("PAUSE");
}

void LPVector::addOld(LPVector vector)                //Operación de += modo antiguo (solo C)
{
    //Si las dimensiones son diferentes , tenemos un problema grave.
    if (n != vector.n)
    {
        cout << "ERROR DIMENSIONAL ENTRE VECTORES\n";
        return;
    }

    for(int i = 0 ; i < n ; ++i)
        v[i] += vector.v[i];
}

void LPVector::addSSE(LPVector vector)                //Operación de += modo ensamblador SSE
{
    //Fast SSE code when float specified
    float* const row0 = (float*) &v[0];
    float* const row1 = (float*) &(vector.v[0]);
    int i = 0;

    //Si las dimensiones son diferentes , tenemos un problema grave.
    if (n != vector.n)
    {
        cout << "ERROR DIMENSIONAL ENTRE VECTORES\n";
        return;
    }

    __asm
    {
        // Carga trozos de los vectores de 4 en 4
        // La carga se hace "desordenada para que siempre haya una instrucción como mínimo entre uso y uso de registros."
        mov      edx, row0
        mov      esi, row1
    }

    i = n;
   
    while( i >= 8 )
    {
        __asm
        {
            //Copiamos el puntero al vector v donde almacenaremos el valor final
            mov        edi,    edx

            //Cargamos la info en los registros SSE
            movups   xmm0, [edx]
            movups   xmm1, [esi]

            //Aumentamos los contadores en 16 = 4 elementos * 4 bytes por elemento (float)
            add         edx,16
            add         esi,16

            //Cargamos la info en los registros SSE(tenemos 8 registros donde guardar info)
            movups   xmm2, [edx]
            movups   xmm3, [esi]

       
            //Hacemos la operación que toca, en nuestro caso, sumar
            addps    xmm0 , xmm1    //Sumamos de 4 en 4
            addps    xmm2 , xmm3    //Sumamos de 4 en 4

            //En edi y edx es donde teniamos cargado los trozo de vector v y es donde pondremos el resultado
            movups     [edi], xmm0
            movups   [edx], xmm2

            //Aumentamos los contadores en 16 = 4 elementos * 4 bytes por elemento (float)
            add     edx,16
            add  esi,16
        }
        i -= 8;
    }
   
    //Los flecos los rematamos de la manera tradicional
    for(int j = n-i ; j < n ; ++j)
        v[j] += vector.v[j];
}

Al ejecutar todo este código tendreis una salida de este estilo:
Version sin optimizar
Tiempo gastado: 321 milisegundos
Version optimizada con SSE
Tiempo gastado : 240 milisegundos
Calculo correcto.

Podreis observar que la ganancia no es comparable a multiplicar por 4 la velocidad como predicen las operaciones SSE sino que se queda en un 25-30% de mejora. Esto es debido a que las operaciones de entrada-salida (movups) gastan muchos recursos y hacen que las operaciones sencillas ( por ejemplo un suma sencilla ) se vean menos mejoradas que las operaciones complicadas ( muchos cálculos y pocos accesos a memoria ).

Espero que os guste, nos vemos

7 comentarios :

  1. Interesante el tema del SSE. thx

    Saludos.

    ResponderEliminar
  2. Pues aún no has visto nada :D. Se pueden llegar a obtener mejoras impresionantes (hasta x10 solo con SSE). A la que pueda os paso ejemplos más espectaculares.

    ResponderEliminar
  3. Pues si la verdad, engines como Id Tech 4 usa mucho el tema de los SSE, ya veo porque sus juegos tienen muy buen redimiendo.

    Saludos.

    ResponderEliminar
  4. Un gran ejemplo... se te ocurre para que podriamos usar las SSE en el GameEngine?

    ResponderEliminar
  5. Jeje pues nose, para todo lo que tenga que ver con la matematica?, que tu eres el experto :D, igual deberia ver como lo implementa en el motor de IdTEch4, vease el codigo del Doom3, quake4 etc:

    ftp://ftp.idsoftware.com/idstuff/

    Saludos.
    StrongCod3r.

    ResponderEliminar
  6. No tenía ni idea de esto, y menos de que se conseguían tan buenos rendimientos, a lo mejor cuando acabe paso todo el framework de operaciones entre vectores matrices :)
    Thx por todo de nuevo!!!

    ResponderEliminar
  7. La combo de SSE y threads da un rendimiento más que bueno. La clave está en la eliminación de los accesos a memoria que son los que limitan la potencia de calculo final. En un 4core se pueden conseguir rendimientos del orden del gigaflop sin ningún tipo de problema.

    ResponderEliminar

Entradas populares