2009-02-25 21 views
26

Tengo un ciclo escrito en C++ que se ejecuta para cada elemento de una gran matriz de enteros. Dentro del ciclo, enmascaré algunos bits del entero y luego encontraré los valores mínimo y máximo. Escuché que si uso las instrucciones de SSE para estas operaciones, se ejecutará mucho más rápido en comparación con un ciclo normal escrito usando condiciones Y de tipo bit, y if-else. Mi pregunta es si debo ir por estas instrucciones de SSE? Además, ¿qué sucede si mi código se ejecuta en un procesador diferente? ¿Seguirá funcionando o estas instrucciones son específicas del procesador?Uso de las instrucciones de SSE

+10

SSE es específico de la arquitectura x86. Además, hay versiones SSE (SSE2, SSE3, ...) y no todas las características SSE están disponibles en todos los procesadores. Usar SSE correctamente definitivamente ofrecerá un gran aumento de rendimiento; pero considere cuidadosamente si requiere esa optimización. –

Respuesta

23
  1. instrucciones SSE son procesador específico. Puede buscar qué procesador admite qué versión de SSE en wikipedia.
  2. Si el código SSE será más rápido o no, depende de muchos factores: el primero es, por supuesto, si el problema está ligado a la memoria o a la CPU. Si el bus de memoria es el cuello de botella, SSE no ayudará mucho. Intenta simplificar tus cálculos enteros, si eso hace que el código sea más rápido, probablemente esté vinculado a la CPU, y tienes muchas posibilidades de acelerarlo.
  3. Tenga en cuenta que escribir código SIMD es mucho más difícil que escribir código C++, y que el código resultante es mucho más difícil de cambiar. Mantenga siempre actualizado el código C++, lo querrá como comentario y para verificar la corrección de su código ensamblador.
  4. Piense en utilizar una biblioteca como el IPP, que implementa operaciones SIMD comunes de bajo nivel optimizadas para varios procesadores.
+5

"Si el bus de memoria es el cuello de botella, SSE no ayudará mucho". - Eso es ignorar las operaciones de memoria de transmisión. – MSalters

2

Las instrucciones de SSE estaban originalmente solo en chips Intel, pero recientemente (¿desde Athlon?) AMD también las admite, por lo que si codificas contra el conjunto de instrucciones SSE, deberías ser portátil para la mayoría de los procesadores x86.

Dicho esto, puede que no valga la pena aprender a codificar SSE a menos que ya esté familiarizado con el ensamblador en x86. Una opción más fácil podría ser verificar los documentos del compilador y ver si hay opciones para permitir el compilador para autogenerar código SSE por ti. Algunos compiladores vectorizan bucles muy bien de esta manera. (Eres probablemente no sorprenda al saber que los compiladores de Intel hacen un buen trabajo de esta :)

+1

NO es necesario que sepa ensamblar para hacer uso de los intrínsecos simd. p. x = _mm_mul_ps (y, z) multiplica cada uno de los 4 flotantes en y por los 4 flotantes en zy coloca el resultado en x. ¿Qué tan fácil es eso? –

+0

@Mark: Pero esos intrínsecos de SIMD son solo envoltorios alrededor del ensamblaje. Para usarlos, realmente necesita saber cómo funcionan las instrucciones SSE, lo que significa leer sobre las operaciones de la máquina. Entonces necesita saber ensamblaje SSE. –

6

Si utiliza instrucciones SSE, obviamente estás limitado a los procesadores que soportan estos. Eso significa x86, que se remonta al Pentium 2 más o menos (no recuerdo exactamente cuándo se introdujeron, pero es hace mucho tiempo)

SSE2, que, por lo que recuerdo, es el que ofrece operaciones enteras, es algo más reciente (Pentium 3: aunque los primeros procesadores AMD Athlon no los admitieron)

En cualquier caso, tiene dos opciones para utilizar estas instrucciones. O escribe todo el bloque de código en el ensamblado (probablemente una mala idea. Eso hace que sea virtualmente imposible para el compilador optimizar tu código, y es muy difícil para un humano escribir un ensamblador eficiente).

Como alternativa, utilice los intrínsecos disponibles con su compilador (si la memoria no sirve, por lo general están definidos en xmmintrin.h)

Pero, de nuevo, el rendimiento no puede mejorar. El código SSE plantea requisitos adicionales de los datos que procesa. Principalmente, el que hay que tener en cuenta es que los datos deben estar alineados en los límites de 128 bits. También debe haber pocas o ninguna dependencia entre los valores cargados en el mismo registro (un registro SSE de 128 bits puede contener 4 entradas. Agregar el primero y el segundo juntos no es óptimo. Pero agregar las cuatro entradas a las 4 entradas correspondientes en otro registro será rápido)

puede ser tentador utilizar una biblioteca que envuelve todo el bajo nivel SSE tocar el violín, pero que también podría arruinar cualquier beneficio potencial de rendimiento.

No sé qué tan bueno es el soporte de operaciones enteras de SSE, por lo que también puede ser un factor que puede limitar el rendimiento. SSE se dirige principalmente a acelerar las operaciones de coma flotante.

3

Hemos implementado algunos códigos de procesamiento de imágenes, similares a los que describe pero en una matriz de bytes, en SSE. La aceleración en comparación con el código C es considerable, dependiendo del algoritmo exacto más que un factor de 4, incluso con respecto al compilador Intel. Sin embargo, como ya lo mencionó, tiene los siguientes inconvenientes:

  • Portabilidad. El código se ejecutará en cada CPU similar a Intel, por lo que también AMD, pero no en otras CPU. Eso no es un problema para nosotros porque controlamos el hardware de destino. Cambiar los compiladores e incluso a un sistema operativo de 64 bits también puede ser un problema.

  • Tiene una curva de aprendizaje pronunciada, pero descubrí que después de comprender los principios de escribir nuevos algoritmos no es tan difícil.

  • Maintainable. La mayoría de los programadores C o C++ no tienen conocimiento de ensamblado/SSE.

Mi consejo para ti será ir a por ello sólo si realmente necesita la mejora del rendimiento, y no puede encontrar una función para su problema de una biblioteca como la Intel IPP, y si se puede vivir con los problemas de portabilidad.

14

SIMD, de los cuales SSE es un ejemplo, le permite hacer la misma operación en múltiples fragmentos de datos. Por lo tanto, no obtendrá ninguna ventaja de usar SSE como reemplazo directo para las operaciones enteras, solo obtendrá ventajas si puede realizar las operaciones en múltiples elementos de datos a la vez. Esto implica cargar algunos valores de datos que son contiguos en la memoria, realizar el procesamiento requerido y luego pasar al siguiente conjunto de valores en la matriz.

Problemas:

1 Si la ruta de código depende de los datos que están siendo procesados, SIMD se vuelve mucho más difícil de implementar. Por ejemplo:

a = array [index]; 
a &= mask; 
a >>= shift; 
if (a < somevalue) 
{ 
    a += 2; 
    array [index] = a; 
} 
++index; 

no es fácil de hacer como SIMD:

a1 = array [index] a2 = array [index+1] a3 = array [index+2] a4 = array [index+3] 
a1 &= mask   a2 &= mask   a3 &= mask   a4 &= mask 
a1 >>= shift  a2 >>= shift   a3 >>= shift   a4 >>= shift 
if (a1<somevalue) if (a2<somevalue) if (a3<somevalue) if (a4<somevalue) 
    // help! can't conditionally perform this on each column, all columns must do the same thing 
index += 4 

2 Si los datos no son contigous continuación, cargar los datos en las instrucciones SIMD es engorroso

3 El código es procesador específico. SSE solo está en IA32 (Intel/AMD) y no todas las cpus IA32 admiten SSE.

Necesita analizar el algoritmo y los datos para ver si pueden ser SSE'd y eso requiere saber cómo funciona SSE. Hay mucha documentación en el sitio web de Intel.

+5

El problema 1 generalmente se resuelve usando las instrucciones de la máscara SIMD. Algo así como __m128 mask = _mm_cmplt_ps (a, somevalue); a = _mm_add_ps (a, _mm_and_ps (mask, _mm_set_ps1 (2)); for the if (a

1

Aunque es cierto que SSE es específico de algunos procesadores (SSE puede ser relativamente seguro, SSE2 mucho menos en mi experiencia), puede detectar la CPU en tiempo de ejecución y cargar el código dinámicamente dependiendo de la CPU de destino.

0

No recomiendo hacerlo usted mismo a menos que sea bastante hábil con el ensamblaje.El uso de SSE requerirá, más que probable, una reorganización cuidadosa de sus datos, como señala Skizz, y el beneficio a menudo es cuestionable en el mejor de los casos.

Probablemente sea mucho mejor para usted escribir bucles muy pequeños y mantener sus datos muy bien organizados y solo confiar en que el compilador lo haga por usted. Tanto el compilador Intel C y GCC (desde 4.1) pueden auto-vectorizar su código, y probablemente harán un mejor trabajo que usted. (Sólo tiene que añadir -ftree-vectorizar a sus CXXFLAGS.)

Editar: Otra cosa que debo mencionar es que varios compiladores soportan intrínsecos de montaje, que, probablemente, la OMI, ser más fáciles de usar que el ASM() o __asm ​​{} sintaxis.

+0

Todavía tengo que ver que el autovectorizador de GCC hace más bien que dañar, aunque supongo que siempre podría ponte mejor. – Crashworks

+0

Las nuevas versiones siempre progresan en características y funcionalidad. He escuchado que el vectorizador de GCC es bastante bueno, y mejor en la versión 4.3, especialmente ahora que es el predeterminado en -O3. – greyfade

1

SIMD intrínsecos (como SSE2) pueden acelerar este tipo de cosas, pero tomar la experiencia para usar correctamente. Son muy sensibles a la alineación y la latencia de la tubería; el uso descuidado puede empeorar el rendimiento de lo que hubiera sido sin ellos. Obtendrá una aceleración mucho más fácil e inmediata simplemente al usar la captación previa de caché para asegurarse de que todas sus entradas estén en L1 a tiempo para que pueda operar sobre ellas.

A menos que su función necesite un rendimiento superior a 100.000,000 de enteros por segundo, es probable que SIMD no valga la pena.

1

Solo para agregar brevemente lo que se ha dicho antes sobre diferentes versiones SSE disponibles en diferentes CPU: Esto se puede comprobar mirando los indicadores de función respectivos devueltos por la instrucción CPUID (consulte la documentación de Intel para obtener más información).

1

Eche un vistazo a ensamblador en línea para C/C++, aquí hay un DDJ article. A menos que esté 100% seguro de que su programa se ejecutará en una plataforma compatible, debe seguir las recomendaciones que se han dado aquí.

10

Este tipo de problema es un ejemplo perfecto de dónde es esencial un buen perfilador de bajo nivel. (Algo así como VTune) Puede darte una idea mucho más informada de dónde se encuentran tus puntos de acceso.

Mi suposición, de lo que usted describe es que su punto de acceso probablemente sea un error de predicción de bifurcación que resulte de cálculos mínimos/máximos usando if/else. Por lo tanto, el uso de las características intrínsecas de SIMD debería permitirle usar las instrucciones mín./Máx., Sin embargo, podría valer la pena tratar de usar una minuciosidad mínima/máxima sin ramas. Esto podría lograr la mayoría de las ganancias con menos dolor.

Algo como esto:

inline int 
minimum(int a, int b) 
{ 
    int mask = (a - b) >> 31; 
    return ((a & mask) | (b & ~mask)); 
} 
2

Escribir código que ayuda al compilador de entender lo que está haciendo. GCC entender y optimizar el código SSE como este:

typedef union Vector4f 
{ 
     // Easy constructor, defaulted to black/0 vector 
    Vector4f(float a = 0, float b = 0, float c = 0, float d = 1.0f): 
     X(a), Y(b), Z(c), W(d) { } 

     // Cast operator, for [] 
    inline operator float*() 
    { 
     return (float*)this; 
    } 

     // Const ast operator, for const [] 
    inline operator const float*() const 
    { 
     return (const float*)this; 
    } 

    // ---------------------------------------- // 

    inline Vector4f operator += (const Vector4f &v) 
    { 
     for(int i=0; i<4; ++i) 
      (*this)[i] += v[i]; 

     return *this; 
    } 

    inline Vector4f operator += (float t) 
    { 
     for(int i=0; i<4; ++i) 
      (*this)[i] += t; 

     return *this; 
    } 

     // Vertex/Vector 
     // Lower case xyzw components 
    struct { 
     float x, y, z; 
     float w; 
    }; 

     // Upper case XYZW components 
    struct { 
     float X, Y, Z; 
     float W; 
    }; 
}; 

Pero no se olvide de tener -msse2 -msse en sus parámetros de construcción!

3

Puedo decir por experiencia que SSE ofrece una aceleración enorme (4x y superior) sobre una versión simple c del código (no hay asimetría en línea, no se usan intrínsecos) pero el ensamblador optimizado a mano puede vencer el ensamblado generado por compilador el compilador no puede descifrar qué pretendía el programador (en mi opinión, los compiladores no cubren todas las posibles combinaciones de códigos y nunca lo harán). Ah, y el compilador no puede diseñar los datos que se ejecutan a la velocidad más rápida posible. Pero necesita mucha experiencia para una aceleración sobre un compilador Intel (si es posible).

1

Acepto los carteles anteriores. Los beneficios pueden ser bastante grandes, pero obtenerlo puede requerir mucho trabajo. La documentación de Intel en estas instrucciones es más de 4K páginas. Es posible que desee comprobar EasySSE (biblioteca de contenedores de C++ sobre intrínsecos + ejemplos) sin cargo en Ocali Inc.

Supongo que mi afiliación con este EasySSE es clara.

Cuestiones relacionadas