Práctica 4. DESENROLLADO DE BUCLES. Objetivos. Aplicar la técnica del desenrollado de bucles para extraer paralelismo a los programas y aplicar la técnica de reorganización de código para ver cómo mejora el rendimiento de nuestros programas cuando son ejecutados en procesador con múltiples unidades aritméticas. Acostumbrar a los alumnos a preparar la práctica en casa. Tiempo aproximado para realizar esta sesión: 4 horas (los laboratorios son de 2:30 horas). Tutorías lunes y miércoles de 3 a 6 de la tarde. Desenrollado de bucles. Para explicar mejor los conceptos del desenrollamiento de bucles, veamos primero un bucle que realiza una operación vectorial consistente en sumar a cada elemento de un vector un escalar. Loop: LD F0, 0(R1) ADDD F4, F0, F2 0(R1), F4 SUB R1, R1, #8 BNEZ R1, loop El vector comienza en la dirección 0x0000 de memoria y la ejecución de una iteración invierte 9 ciclos. Para sacar un mejor rendimiento al código deberíamos poder obtener mayor paralelismo, es decir, mayor independencia entre instrucciones. Esto se puede conseguir desenrollando el bucle ( loop unrolling ). Se consigue replicando muchas veces el cuerpo del bucle, utilizando diferentes registros para cada réplica. Con ello conseguimos: Más y mayor independencia entre instrucciones que repercute en una mejor planificación de las mismas. Menor número de instrucciones ejecutadas, pues hay menos instrucciones de autoincremento de contadores o registros y condicionales. Veamos como quedaría el bucle si lo desenrollamos 4 veces. Loop: LD F0, 0(R1) ; iteración i ADDD F4, F0, F2 0(R1), F4 LD F6,-8(R1) ; iteración i+1 ADDD F8, F6, F2-8(R1), F8 LD F10,-16(R1) ; iteración i+2 ADDD F12, F10, F2-16(R1), F12 LD F14,-24(R1) ; iteración i+3 ADDD F16, F14, F2-24(R1), F16 SUB R1, R1, #32 ; incrementamos en 4 BNEZ R1,loop Este bucle se ejecutará en 27 ciclos de reloj, pero un cuarto de veces menos que el bucle original. La ventaja es que ahora sobre este bucle desenrollado es más fácil hacer una buena planificación. Veamos una posible forma de planificarlo, consistente en agrupar las operaciones de carga, luego agrupamos todas las aritméticas y por último agrupamos todas las de salvar en memoria. Esta es una planificación Vicente Arnau 1 23/11/2010
sencilla, pero muy eficiente. Obsérvese que los registros punteros, solo son actualizados una vez dentro del bucle y que los registro de cada iteración desenrollada son necesariamente diferentes. ;----------------------> Planificación sencilla: Loop: LD F0, 0(R1) LD F6,-8(R1) LD F10,-16(R1) LD F14,-24(R1) ADDD ADDD ADDD ADDD F4, F0, F2 F8, F6, F2 F12, F10, F2 F16, F14, F2 0(R1), F4-8(R1), F8-16(R1), F12-24(R1), F16 SUB R1, R1, #32 BNEZ R1,loop Al desenrollarlo, el bucle ahora se ejecuta en 14 ciclos de reloj, pero hay 4 veces menos iteraciones, pues k=4. Si consideramos que el bucle original poseía 16 iteraciones, los tiempos de ejecución serian los siguientes: Nº ciclos Velocidad Bucle inicial 9*16= 144 1 Bucle desenrollado 27*4= 108 1.333 Bucle desenrollado y planificado 14*4= 56 2.57 (No hacer mucho caso de estos tiempo, son tiempos especulativos). Pregunta: Pero, qué ocurre si el número de iteraciones del bucle original no es un múltiplo perfecto del número de veces que vamos a desenrollar el bucle? Respuesta: Si tenemos n iteraciones en un bucle y lo queremos desenrollar K veces, este lo descompondremos en dos bucles: 1º) El primero desde 1 hasta (n módulo K), que será el desenrollado. 2º) Desde 1 hasta (n div K) tendremos iteraciones fuera del bucle desenrollado, que también podremos planificar. Veremos en hojas contiguas varios ejemplos de programas sobre los cuales se ha aplicado esta técnica. Veremos también un gráfico con los resultados en el ámbito de tiempo de ejecución de los programas. Vicente Arnau 2 23/11/2010
;================================ ; MULT_01.S : Programa ejemplo = ;================================ ; zona de datos datos:.float 1.0, 1.5, 3.0, 3.5, 4.5,.float 5.0, 5.5, 7.0, 8.0, 8.5, CTE:.FLOAT 10.0 ;Zona de código.global main main: R16, R0, 16 bucle: LF LF F2, CTE ; Leo CTE F0, datos(r1) F4, F0, F2 datos(r1), F4 R1, R1, 4 R16, R16, -1 BNEZ R16, bucle ;========================================= ; MULT_03.S : PLANIFICACIÓN SENCILLA = ;========================================= datos:.float 1.0, 1.5, 2.0, 2.5, 3.0,.float 5.0, 5.5, 6.0, 6.5, 7.0, CTE:.FLOAT 10.0 main: R16,R0,16 ;4*4 itera. LF F2, CTE bucle: LF LF LF LF BNEZ F4, datos+0(r1) F6, datos+4(r1) F8, datos+8(r1) F10, datos+12(r1) F5,F4,F2 F7,F6,F2 F9,F8,F2 F11,F10,F2 datos+0(r1),f5 datos+4(r1),f7 datos+8(r1),f9 datos+12(r1),f11 R1,R1,16 R16,R16,-4 R16,bucle ;========================================== ; MULT_02.S : Bucle DESENROLLADO SIN MAS! = ;========================================== datos:.float 1.0, 1.5, 2.0, 2.5, 3.0,.float 5.0, 5.5, 6.0, 6.5, 7.0, CTE:.FLOAT 10.0 main: R16,R0,16 ; 4*4 itera. LF F2, CTE ; Leo cte. bucle: LF F4, datos+0(r1) F5,F4,F2 datos+0(r1),f5 LF F6,datos+4(R1) F7,F6,F2 datos+4(r1),f7 LF F8,datos+8(R1) F9,F8,F2 datos+8(r1),f9 LF F10,datos+12(R1) F11,F10,F2 datos+12(r1),f11 BNEZ R16,R16,-4 R1,R1,16 R16,bucle ;========================================== ; MULT_04.S : MUY PLANIFICADO ;========================================== ; datos:.float 1.0, 1.5, 2.0, 2.5, 3.0,.float 5.0, 5.5, 6.0, 6.5, 8.5, CTE:.FLOAT 10.0 main: R16,R0,16 ;4*4 itera. R1,R0,datos LF F2, CTE bucle: LF F4, datos+0(r1) LF F6, datos+4(r1) F5,F4,F2 LF F8, datos+8(r1) F7,F6,F2 LF F10, datos+12(r1) F9,F8,F2 datos+0(r1),f5 F11,F10,F2 datos+4(r1),f7 datos+8(r1),f9 R16,R16,-4 ; ojo! datos+12(r1),f11 R1, R1, 16 BNEZ R16,bucle ;Si R11<>0 salto. La mejora conseguida con esta técnica es muy elevada. Pero dependerá del número de unidades aritméticas y de la duración de las mismas, el que tengamos que realizar la planificación final de forma distinta para cada caso y configuración. Vicente Arnau 3 23/11/2010
Realización. En esta práctica se utilizarán los dos programas nuevos: Series.s y Matrices.s. Sobre ellos se realizarán varios desenrollamientos (con valores de K diferentes). Sobre los programas desenrollados habrá que realizar análisis de cómo varía la aceleración con la variación del número de unidades funcionales. La opción forwarding deberá estar siempre activada. La configuración de las unidades funcionales inicialmente será la que aparece en la figura lateral izquierda. Los tiempos de retardo serán de 4, 9 y 14 ciclos. Posteriormente modificaremos en número de unidades funcionales para ver su repercusión en la ejecución de los programas. También se realizará una PLANIFICACIÓN del código generado para acelerar su ejecución. Importante: la planificación se realizará de forma diferente según dispongamos de más o menos unidades funcionales. Para cada configuración habrá que realizar una planificación a medida. Las dos configuraciones que vamos a utilizar son las dos que aparecen en la siguiente figura. A con una unidad de cada y B con varias unidades aritméticas de cada tipo posible. Configuración A Configuración B Programas. Series: Tenemos tres series X(n), Y(n) y Z(n) de n=16 datos en formato coma flotante simple precisión almacenados a partir de la posición de memoria 0x1000. Y se pide calcular el valor de la serie W(n), tal que: X (n) = [Y(n) * cte_2] + [ X(n) * cte_1] ; Y (n) = X(n) + [X (n) / cte_2] ; Z (n) = Z(n) * cte_1 ; W(n) = X (n) + Y (n) + Z (n) Tener muy en cuenta que para cada configuración debemos mostrar los ciclos alcanzados cuando ejecutamos el programa en 4 condiciones diferentes: i. Sin desenrollar, con el programa sin ninguna modificación ni planificación. ii. Desenrollado y sin ninguna planificación. iii. Desenrollando y con la planificación sencilla que consiste en agrupar las instrucciones de carga, luego las aritméticas y luego las de salvar datos. iv. Sobre este último programa desenrollado, analizar la forma con que se ejecuta el código (ventana clock cicle diagram ), ver las dependencias de datos entre las instrucciones y planificar la emisión de las mismas intentando que el programa se ejecute de la forma más rápida posible. Vicente Arnau 4 23/11/2010
Se deberá comprobar en todo momento que los resultados de la ejecución del programa (serie W(n)) no cambian tras ningún desenrollamiento ni ninguna planificación. Para lo cual, después de la ejecución del código de la configuración B y bajo condición iv, se deberá anotar en la siguiente tabla el resultado obtenido para la serie W(n). W[0]= W[1]= W[2]= W[3]= W[4]= W[5]= W[6]= W[7]= W[8]= W[9]= W[10]= W[11]= W[12]= W[13]= W[14]= W[15]= 2) Multiplicación de Matrices 4*4 con 3 bucles: Dadas dos matrices de 4*4 elementos MaX y MaY, realizar un programa que mediante el uso de tres bucles, las multiplique y almacene el resultado en etiqueta MaZ. El bucle más interno recorre los elementos de una fila de la matriz A y los va multiplicando por los elementos de una columna de la matriz B. Para este ejemplo, el bloque Salvar_datos(i) de la iteración del bucle más interno está formado por la instrucción que almacena en un registro temporalmente el resultado de la multiplicación de cada elemento de una fila por cada elemento de columna. El bloque EXE(i) estará formado solo por la instrucción de multiplicar. El bloque Leer(i) estará formado por 2 instrucciones de carga. Para que quede más claro, observar detenidamente el código que aparece en el recuadro, pues el programa en ensamblador debe ser fiel reflejo del algoritmo de multiplicación de matrices que se muestra en él. MaZ = MaX * MaY For (i=1 to 4){ For (j=1 to 4) { TEMP=0; For (k=1 to 4) TEMP = MaX[i][k]*MaY[k][j] + TEMP; MaZ[i, j] = TEMP; } } El programa en ensamblador deberá tener 3 bucles necesariamente. En el siguiente cuadro se deberá anotar el código ensamblador de DLX que implementa el triple bucle realizado para multiplicar las dos matrices. Al programa resultante se le deberá aplicar la técnica de desenrollamiento de bucles, pero solo al bucle más interno. Puedes anotar el código desarrollado en el siguiente cuadro. Nota: Las dos hojas siguientes deberán entregarse para la evaluación de la práctica. Además se tomará nota de los tiempos empleados en la ejecución de cada código. En la Página web http://www.uv.es/~varnau/aic_2009-10.htm podéis encontrar las plantillas que debéis utilizar para realizar los dos programas. P4_P1.s y P4_P2.s. Y en la siguiente página podeis ver la estructura que debe tener vuestro programa: tres bucles anidados, que se repiten 4 veces y que recorren la primera matriz por filas y la segunda por columnas. Importante: Para pasar de un elemento de una fila al siguiente hay que sumarle 4 al puntero de fila. Para pasar de un elemento de una columna al siguiente hay que sumarle 16 al puntero de columna. Vicente Arnau 5 23/11/2010
Nom: Nom: ; Programa en DLX sin desenrollar para Matrices. Completar el siguiente código. ;============================================================ ; M A T R I Z _ 4 * 4. S : Multiplicación de matrices 4*4 = ; Lab. AIC. Curso 2010-2011 = ;============================================================ ;Datos a partir de esta dirección nn:.word 4, 0, 0, 0 MaX:.word 1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 9, 9, 7, 5, 3, 1 MaY:.word 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 MaZ:.space 16*4 ;Zona de código en dirección 0x100 main: lw r4, nn ; contador de filas bu1: lw r8, nn ; contador de columnas bu2: lw r14, nn ; elemento de fila por columna bu3: LW R1, MaX(R25) ; Cargo a(i,j) LW R2, MaY(R27) ; Cargo b(i,j) MULT R3, R1, R2 BNEZ R14, bu3 SW MaZ(R24), R10 ; salvo datos BNEZ R8, bu2 BNEZ R4, bu1 ; FIN Vicente Arnau 6 23/11/2010
;============================================================== ; M A T R I Z _ 4 * 4. S : Multiplicación de matrices 4*4 = ; Programa desenrollado con k=2 Lab. AIC. Curso 2010-2011 = ;============================================================== ;Datos a partir de esta dirección nn:.word 4, 0, 0, 0 MaX:.word 1, 2, 3, 4, 5, 6, 7, 8, 9, 9, 9, 9, 7, 5, 3, 1 MaY:.word 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 MaZ:.space 16*4 ;Zona de código en dirección 0x100 main: lw r4, nn ; contador de filas ; FIN Vicente Arnau 7 23/11/2010
Nom: Nom: Series.S Series desenrollado k=2 Series desenrollado con planificación sencilla. Configuración A Configuración B Nº ciclos/velocidad Nº instrucciones Nº ciclos/velocidad Nº instrucciones Series desenrollado con planificación TOTAL. No realizar No realizar Series.S Series desenrollado k = 4 Configuración A Configuración B Nº ciclos/velocidad Nº instrucciones Nº ciclos/velocidad Nº instrucciones Planificación sencilla. No realizar No realizar Planificación TOTAL. No realizar No realizar Configuración A Configuración B Nº ciclos/velocidad Nº instrucciones Nº ciclos/velocidad Nº instrucciones Matrices.S Matrices desenrollado k=2 Planificación TOTAL. La velocidad del programa inicial vale 1. La velocidad del programa desenrollado es la velocidad relativa en Nº de ciclos respecto al programa inicial. Es decir, dividir los ciclos del programa inicial por los ciclos del programa desenrollado. Importante: En el transcurso de la práctica se deberá mostrar al profesor que los programas desenrollados funcionan correctamente. Es decir, que los programas desenrollados dan los mismos resultados numéricos que los programas sin desenrollar, pero en un número menor de ciclos. La nota final dependerá de los apartados que se completen y de los ciclos que necesite el programa desenrollado y planificado para ejecutarse. Para no volverme loco, solo miraré la ejecución de los apartados sombreados en azul claro. Vicente Arnau 8 23/11/2010