Pregunta

¿Es una buena idea vectorizar el código? ¿Cuáles son las buenas prácticas en términos de cuándo hacerlo? ¿Qué pasa debajo?

¿Fue útil?

Solución

La vectorización significa que el compilador detecta que sus instrucciones independientes se pueden ejecutar como una Simd instrucción. El ejemplo habitual es que si haces algo como

for(i=0; i<N; i++){
  a[i] = a[i] + b[i];
}

Se vectorizará como (usando notación vectorial)

for (i=0; i<(N-N%VF); i+=VF){
  a[i:i+VF] = a[i:i+VF] + b[i:i+VF];
}

Básicamente, el compilador elige una operación que se puede hacer en los elementos VF de la matriz al mismo tiempo y hace estos tiempos N/VF en lugar de hacer la operación única n veces.

Aumenta el rendimiento, pero da más requisitos sobre la arquitectura.

Otros consejos

Como se mencionó anteriormente, la vectorización se utiliza para utilizar las instrucciones SIMD, que pueden realizar operaciones idénticas de diferentes datos empaquetados en grandes registros.

Una guía genérica para permitir que un compilador se autovectorice un bucle es garantizar que no haya elementos de datos B/W de flujo y anti-dependencias en diferentes iteraciones de un bucle.

http://en.wikipedia.org/wiki/data_depencency

Algunos compiladores como los compiladores Intel C ++/Fortran son capaces de autovectorizar el código. En caso de que no pudiera vectorizar un bucle, el compilador Intel es capaz de informar por qué no pudo hacer eso. Los informes se pueden usar para modificar el código de manera que se vuelva vectorizable (suponiendo que sea posible)

Las dependencias están cubiertas en profundidad en el libro 'Optimización de los compiladores para las arquitecturas modernas: un enfoque basado en dependencia'

Es la generación de código SSE.

Tiene un bucle con código de matriz flotante en Matrix1 [i] [j] + matrix2 [i] [j] y el compilador genera código SSE.

La vectorización no necesita limitarse al registro único que puede contener datos grandes. Como usar el registro de bit '128' para contener datos de bit '4 x 32'. Depende de las limitaciones arquitectónicas. Algunas arquitectura tienen diferentes unidades de ejecución que tienen sus propios registros. En ese caso, una parte de los datos se puede alimentar a esa unidad de ejecución y el resultado puede tomarse de un registro correspondiente a esa unidad de ejecución.

Por ejemplo, considere el siguiente caso.

para (i = 0; i <n; i ++)
{
a [i] = a [i] + b [i];
}



Si estoy trabajando en una arquitectura que tiene dos unidades de ejecución, entonces mi tamaño de vector se define como dos. El bucle mencionado anteriormente se refirará como

para (i = 0; i <(n/2); i+= 2)
{
a [i] = a [i] + b [i];


a [i+1] = a [i+1]+b [i+1];
}

Nota: El 2 dentro de la declaración For se deriva del tamaño del vector.

Como tengo dos unidades de ejecución, las dos declaraciones dentro del bucle se alimentarán en las dos unidades de ejecución. La suma se acumulará en las unidades de ejecución por separado. Finalmente, se llevará a cabo la suma de valores acumulados (de dos unidades de ejecución).

Las buenas prácticas son
1. Las restricciones como la dependencia (entre diferentes iteraciones del bucle) deben verificarse antes de vectorizar el bucle.
2. Las llamadas de función deben prevenirse.
3. El acceso al puntero puede crear alias y debe prevenirse.

Tal vez también eche un vistazo a LBSImDX86 (código fuente).

Un buen ejemplo bien explicado es:

Elegir evitar ramas: un pequeño ejemplo de altivec

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