Pregunta

Tengo un bucle escrito en C++ que se ejecuta para cada elemento de una matriz de enteros grande.Dentro del ciclo, enmascaro algunos bits del número entero y luego encuentro los valores mínimo y máximo.Escuché que si uso instrucciones SSE para estas operaciones, se ejecutará mucho más rápido en comparación con un bucle normal escrito usando condiciones AND bit a bit y if-else.Mi pregunta es ¿debería seguir 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?

¿Fue útil?

Solución

  1. instrucciones SSE son procesador específico. Se puede consultar qué procesador es compatible con la versión SSE en la wikipedia.
  2. Si el código SSE será más rápido o no depende de muchos factores: El primero de ellos es, por supuesto, si el problema es la memoria-dependiente o vinculado a la CPU. Si el bus de memoria es el cuello de botella SSE no ayuda mucho. Intente simplificar los cálculos de enteros, si eso tiene el código más rápido, es probablemente vinculado a la CPU, y usted tiene una buena oportunidad de acelerarlo.
  3. Tenga en cuenta que la escritura de código SIMD es mucho más difícil de escribir C ++ - código, y que el código resultante es mucho más difícil de cambiar. Siempre mantenga el código C ++ al día, usted querrá como un comentario y para comprobar la corrección de su código ensamblador.
  4. pensar en usar una biblioteca como la IPP, que implementa las operaciones SIMD de bajo nivel comunes optimizadas para varios procesadores.

Otros consejos

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 a la utilización de SSE como un reemplazo directo para las operaciones con enteros, sólo obtendrá ventajas si se puede hacer las operaciones en múltiples elementos de datos a la vez. Esto implica cargar algunos valores de datos que son contiguos en la memoria, haciendo el procesamiento requerido y luego dando un paso al siguiente conjunto de valores de la matriz.

Problemas:

1 Si la ruta de código depende de los datos que están siendo procesados, SIMD se hace 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 es contigous entonces la carga de los datos en las instrucciones SIMD es engorroso

3 El código es específico del procesador. SSE es sólo en IA32 (Intel / AMD) y no todo el apoyo IA32 CPUs SSE.

Es necesario analizar el algoritmo y los datos para ver si se puede SSE'd y que es necesario conocer cómo funciona SSE. Hay un montón de documentación en el sitio web de Intel.

Este tipo de problema es un ejemplo perfecto de donde un buen perfilador de bajo nivel es esencial. (Algo así como VTune) Se le puede dar una idea mucho más informada de dónde se encuentran sus puntos de acceso.

Mi conjetura, de lo que se describe es que su punto de acceso será probablemente fallos de predicción de saltos que resultan de cálculos min / max usando if / else. Por lo tanto, el uso de los intrínsecos SIMD se le permite utilizar las instrucciones min / max, sin embargo, podría valer la pena tratando de utilizar un min / max caluculation sin sucursales en su lugar. Esto podría alcanzar 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));
}

Si utiliza instrucciones SSE, obviamente estás limitado a los procesadores que soportan estos. Eso significa x86, que data del Pentium 2 o menos (no recuerdo exactamente cuando fueron introducidos, pero fue hace mucho tiempo)

SSE2, que, por lo que yo puedo recordar, es el que ofrece operaciones con enteros, es algo más reciente (Pentium 3? A pesar de que los primeros procesadores AMD Athlon no apoyaron a)

En cualquier caso, usted tiene dos opciones para el uso de estas instrucciones. O bien escribir todo el bloque de código en el montaje (probablemente una mala idea. Eso hace que sea prácticamente imposible que el compilador para optimizar su código, y es muy difícil para un ser humano para escribir 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 mejora. código SSE plantea requisitos adicionales de los datos que procesa. Principalmente, el uno a tener en cuenta es que los datos deben estar alineados en límites de 128 bits. También debe haber pocas o ninguna dependencias entre los valores cargados en el mismo registro (un registro SSE 128 bits puede contener 4 ints. La adición de la primera y la segunda juntas no es óptima. Pero la adición de los cuatro enteros en los correspondientes 4 ints en otro registro sea rápida)

Puede ser tentador usar 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é cómo el apoyo operación número entero de buena SSE es, por lo que también puede ser un factor que puede limitar el rendimiento. SSE se dirige principalmente a la aceleración de las operaciones de punto flotante.

Si se va a utilizar Microsoft Visual C ++, debe leer esto:

http://www.codeproject.com/KB/recipes/sseintro.aspx

Hemos implementado un código de procesamiento de imágenes, similar a lo que usted describe, pero en una matriz de bytes, en SSE. El aumento de velocidad en comparación con el código C es considerable, dependiendo del algoritmo exacto más de un factor de 4, incluso en relación con el compilador de Intel. Sin embargo, como ya lo ha dicho usted tiene los siguientes inconvenientes:

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

  • Usted tiene una curva de aprendizaje, pero he encontrado que después de comprender los principios que escriben nuevos algoritmos no es tan difícil.

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

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

que puedo decir de mi experiencia ó que SSE trae una enorme (4x o más) aumento de velocidad sobre una versión c llanura del código (sin asm en línea, no hay intrínsecos utilizados), pero ensamblador optimizado a mano se puede superar ensamblador generado por el compilador si el compilador no puede averiguar lo que el programador previsto (creedme, los compiladores no cubren todas las posibles combinaciones de códigos y que nunca lo hará). Ah, y, el compilador puede no cada layout los datos que se ejecuta en el de mayor velocidad posible. Pero se necesita mucha experiencia ó para un aumento de velocidad durante un procesador Intel compilador (si es posible).

instrucciones SSE eran originalmente sólo en los chips de Intel, pero recientemente (desde Athlon?) AMD compatible con ellas, así que si lo hace el código contra el conjunto de instrucciones SSE, debe ser portátil para la mayoría de los procs x86.

Una vez dicho esto, puede que no sea digno de su tiempo para aprender codificación SSE a no ser que ya está familiarizado con el ensamblador de x86 - una opción más fácil podría ser la de revisar sus documentos compilador y ver si hay opciones para permitir que el compilador autogenerar código de SSE para usted. Algunos compiladores hacen muy bien vectorizar bucles de esta manera. (Usted probablemente no sorprenda al saber que los compiladores de Intel hacen un buen trabajo de esto:)

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!

Si bien es cierto que SSE es específico para algunos procesadores (ESS puede ser relativamente segura, SSE2 mucho menos en mi experiencia), que puede detectar la CPU en tiempo de ejecución, y cargar el código de forma dinámica en función de la CPU de destino.

intrínsecos SIMD (como SSE2) pueden acelerar este tipo de cosas, pero hasta tener experiencia para utilizar correctamente. Son muy sensibles a la alineación y la latencia de la tubería; usar de manera incorrecta puede hacer que el rendimiento aún peor de lo que habría sido sin ellos. Usted obtendrá una aceleración mucho más fácil y más inmediata del simple uso de la obtención previa de caché para asegurarse de que todos los enteros están en L1 a tiempo para que usted pueda operar en ellos.

A menos que su función necesita un rendimiento de más de 100.000.000 enteros por segundo, SIMD probablemente no vale la pena para usted.

Sólo para añadir brevemente a lo que se ha dicho antes de estar disponible en diferentes CPUs acerca de las diferentes versiones de la ESS: Esto se puede comprobar mirando las respectivas banderas de función que devuelve la instrucción CPUID (véase por ejemplo la documentación de Intel para más detalles)

Tener un vistazo a línea ensamblador para C / C ++, aquí es un DDJ artículo . A menos que esté 100% seguro de que su programa se ejecuta en una plataforma compatible debe seguir las recomendaciones muchos han dado aquí.

Estoy de acuerdo con los críticos anteriores. Los beneficios pueden ser bastante grandes, pero para conseguirlo puede requerir mucho trabajo. documentación de Intel en estas instrucciones es más de páginas de 4K. Es posible que desee comprobar hacia fuera EasySSE (c envoltorios ++ biblioteca sobre los intrínsecos + ejemplos) libre de Ocali Inc.

Asumo mi afiliación con este EasySSE es clara.

No recomiendo hacerlo usted mismo a menos que sea bastante competente con el montaje.Lo más probable es que el uso de SSE requiera una cuidadosa reorganización de sus datos, ya que esquivar señala, y el beneficio es a menudo cuestionable en el mejor de los casos.

Probablemente sería mucho mejor para usted escribir bucles muy pequeños y mantener sus datos muy bien organizados y simplemente confiar en que el compilador lo haga por usted.Tanto el compilador Intel C como GCC (desde 4.1) pueden vectorizar automáticamente su código y probablemente harán un mejor trabajo que usted.(Simplemente agregue -ftree-vectorize a su CXXFLAGS).

Editar:Otra cosa que debo mencionar es que varios compiladores admiten intrínsecos del ensamblaje, que probablemente, en mi opinión, sería más fácil de usar que la sintaxis asm() o __asm{}.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top