SSE SIMD optimización de bucles
-
04-10-2019 - |
Pregunta
Tengo algo de código en un bucle
for(int i = 0; i < n; i++)
{
u[i] = c * u[i] + s * b[i];
}
Así, U y b son vectores de la misma longitud, y c y s son escalares. ¿Es este código a un candidato bueno para la vectorización para su uso con SSE con el fin de conseguir un aumento de velocidad?
Actualizar
he aprendido vectorización (resulta que no es tan difícil si se utiliza intrínsecos) e implementado en mi bucle de SSE. Sin embargo, cuando se establecía la marca SSE2 en el compilador de VC ++, consigo sobre el mismo rendimiento que con mi propio código de SSE. El compilador de Intel, por otro lado era mucho más rápido que mi código SSE o VC ++ compilador.
Este es el código que escribí para referencia
double *u = (double*) _aligned_malloc(n * sizeof(double), 16);
for(int i = 0; i < n; i++)
{
u[i] = 0;
}
int j = 0;
__m128d *uSSE = (__m128d*) u;
__m128d cStore = _mm_set1_pd(c);
__m128d sStore = _mm_set1_pd(s);
for (j = 0; j <= i - 2; j+=2)
{
__m128d uStore = _mm_set_pd(u[j+1], u[j]);
__m128d cu = _mm_mul_pd(cStore, uStore);
__m128d so = _mm_mul_pd(sStore, omegaStore);
uSSE[j/2] = _mm_add_pd(cu, so);
}
for(; j <= i; ++j)
{
u[j] = c * u[j] + s * omegaCache[j];
}
Solución
Sí, este es un excelente candidato para la vectorización. Pero, antes de hacerlo, asegúrese de que ha perfila el código para estar seguro de que este es en realidad un valor de optimización. Dicho esto, la vectorización sería algo como esto:
int i;
for(i = 0; i < n - 3; i += 4)
{
load elements u[i,i+1,i+2,i+3]
load elements b[i,i+1,i+2,i+3]
vector multiply u * c
vector multiply s * b
add partial results
store back to u[i,i+1,i+2,i+3]
}
// Finish up the uneven edge cases (or skip if you know n is a multiple of 4)
for( ; i < n; i++)
u[i] = c * u[i] + s * b[i];
Para aún más el rendimiento, se puede considerar la obtención previa de otros elementos de la matriz, y / o desenrollar el bucle y el uso de software pipelining para intercalar el cálculo en un bucle con el accesos a memoria a partir de una iteración diferente.
Otros consejos
Probablemente sí, pero hay que compilador de ayuda con algunos consejos.
__restrict__
colocado sobre los punteros dice compilador que no hay ningún alias entre los dos punteros.
si sabe alineación de sus vectores, comunicar que al compilador (Visual C ++ puede tener alguna facilidad).
No estoy familiarizado con Visual C ++ a mí mismo, pero he oído que no es bueno para la vectorización. Considere el uso de Intel compilador lugar. Intel permite bastante el control preciso sobre generada montaje: http://www.intel.com/software/products/compilers/docs/clin/main_cls/cref_cls/common/cppref_pragma_vector.htm
_mm_set_pd
no está vectorizada. Si se toma literalmente, lee los dos dobles usando operaciones escalares, entonces combina las dos dobles escalares y los copia en el registro SSE. Uso _mm_load_pd
lugar.
Sí, esto es un gran candidato para vectorizaton, suponiendo que no hay superposición de U y B matriz. Sin embargo, el código está limitado por el acceso a memoria (carga / almacenamiento). Vectorización ayuda a reducir los ciclos por ciclo, pero las instrucciones se detendrá debido a la caché-miss en U y B matriz. El / C Compilador Intel C ++ genera el siguiente código con banderas por defecto para procesador x5500 Xeon. El compilador desenrolla el bucle por 8 y emplea SIMD ADD (addpd) e instrucciones multiplicar (mulpd) utilizando xmm [0-16] registros SIMD. En cada ciclo, el procesador puede emitir instrucciones SIMD 2 rendimiento de 4 vías escalar ILP, suponiendo que tiene los datos dispuestos en los registros.
Aquí U, B, C y S son de doble precisión (8 bytes).
..B1.14: # Preds ..B1.12 ..B1.10
movaps %xmm1, %xmm3 #5.1
unpcklpd %xmm3, %xmm3 #5.1
movaps %xmm0, %xmm2 #6.12
unpcklpd %xmm2, %xmm2 #6.12
# LOE rax rcx rbx rbp rsi rdi r8 r12 r13 r14 r15 xmm0 xmm1 xmm2 xmm3
..B1.15: # Preds ..B1.15 ..B1.14
movsd (%rsi,%rcx,8), %xmm4 #6.21
movhpd 8(%rsi,%rcx,8), %xmm4 #6.21
mulpd %xmm2, %xmm4 #6.21
movaps (%rdi,%rcx,8), %xmm5 #6.12
mulpd %xmm3, %xmm5 #6.12
addpd %xmm4, %xmm5 #6.21
movaps 16(%rdi,%rcx,8), %xmm7 #6.12
movaps 32(%rdi,%rcx,8), %xmm9 #6.12
movaps 48(%rdi,%rcx,8), %xmm11 #6.12
movaps %xmm5, (%rdi,%rcx,8) #6.3
mulpd %xmm3, %xmm7 #6.12
mulpd %xmm3, %xmm9 #6.12
mulpd %xmm3, %xmm11 #6.12
movsd 16(%rsi,%rcx,8), %xmm6 #6.21
movhpd 24(%rsi,%rcx,8), %xmm6 #6.21
mulpd %xmm2, %xmm6 #6.21
addpd %xmm6, %xmm7 #6.21
movaps %xmm7, 16(%rdi,%rcx,8) #6.3
movsd 32(%rsi,%rcx,8), %xmm8 #6.21
movhpd 40(%rsi,%rcx,8), %xmm8 #6.21
mulpd %xmm2, %xmm8 #6.21
addpd %xmm8, %xmm9 #6.21
movaps %xmm9, 32(%rdi,%rcx,8) #6.3
movsd 48(%rsi,%rcx,8), %xmm10 #6.21
movhpd 56(%rsi,%rcx,8), %xmm10 #6.21
mulpd %xmm2, %xmm10 #6.21
addpd %xmm10, %xmm11 #6.21
movaps %xmm11, 48(%rdi,%rcx,8) #6.3
addq $8, %rcx #5.1
cmpq %r8, %rcx #5.1
jl ..B1.15 # Prob 99% #5.1
depende de cómo ha colocado u y B en la memoria. si ambos bloques de memoria están lejos el uno del otro, no sería SSE a aumentar mucho en este escenario.
se sugiere que la matriz U y B son AOE (matriz de la estructura) en lugar de SOA (estructura de la matriz), porque se puede cargar tanto de ellos en el registro de instrucción individual.