Esta pregunta continúa sobre mi pregunta aquí (con el consejo de Mystical):
Rendimiento del bucle de código C
Continuando con mi pregunta, cuando uso instrucciones empaquetadas en lugar de instrucciones escalares, el código que usa intrínsecos se vería muy similar:
for(int i=0; i<size; i+=16) {
y1 = _mm_load_ps(output[i]);
…
y4 = _mm_load_ps(output[i+12]);
for(k=0; k<ksize; k++){
for(l=0; l<ksize; l++){
w = _mm_set_ps1(weight[i+k+l]);
x1 = _mm_load_ps(input[i+k+l]);
y1 = _mm_add_ps(y1,_mm_mul_ps(w,x1));
…
x4 = _mm_load_ps(input[i+k+l+12]);
y4 = _mm_add_ps(y4,_mm_mul_ps(w,x4));
}
}
_mm_store_ps(&output[i],y1);
…
_mm_store_ps(&output[i+12],y4);
}
El rendimiento medido de este kernel es de aproximadamente 5.6 operaciones FP por ciclo, aunque esperaría que sea exactamente 4 veces el rendimiento de la versión escalar, es decir, 4.1,6 = 6,4 operaciones FP por ciclo.
Teniendo en cuenta el movimiento del factor de peso (gracias por señalarlo), el programa se ve así:
Parece que el programa no cambia, aunque hay una instrucción adicional después de la movss
operación que mueve el valor de peso escalar al registro XMM y luego usa shufps
para copiar este valor escalar en todo el vector. Parece que el vector de peso está listo para usarse por el mulps
momento, teniendo en cuenta la latencia de conmutación de la carga al dominio de punto flotante, por lo que esto no debería incurrir en ninguna latencia adicional.
Las instrucciones movaps
(alineadas, empaquetadas) addps
y mulps
que se usan en este kernel (verificadas con código ensamblador) tienen la misma latencia y rendimiento que sus versiones escalares, por lo que tampoco deberían incurrir en una latencia adicional.
¿Alguien tiene una idea de dónde se gasta este ciclo adicional por 8 ciclos, asumiendo que el rendimiento máximo que puede obtener este kernel es 6.4 operaciones FP por ciclo y se está ejecutando a 5.6 operaciones FP por ciclo?
Por cierto, aquí está el aspecto del ensamblaje real:
…
Block x:
movapsx (%rax,%rcx,4), %xmm0
movapsx 0x10(%rax,%rcx,4), %xmm1
movapsx 0x20(%rax,%rcx,4), %xmm2
movapsx 0x30(%rax,%rcx,4), %xmm3
movssl (%rdx,%rcx,4), %xmm4
inc %rcx
shufps $0x0, %xmm4, %xmm4 {fill weight vector}
cmp $0x32, %rcx
mulps %xmm4, %xmm0
mulps %xmm4, %xmm1
mulps %xmm4, %xmm2
mulps %xmm3, %xmm4
addps %xmm0, %xmm5
addps %xmm1, %xmm6
addps %xmm2, %xmm7
addps %xmm4, %xmm8
jl 0x401ad6 <Block x>
…
shufps
instrucción agrega 1 ciclo cada 1.6 iteraciones?" Eso es difícil ...