630 likes | 813 Views
ARQUITECTURA DE COMPUTADORES I. Práctica 3. Optimización de Código. Departamento de Arquitectura y Tecnología de Computadores. E.T.S. Ingeniería Informática. Julio Ortega Lopera. Curso 2008/2009. Bibliografía. http://www.intel.com/design/PentiumIII/manuals/
E N D
ARQUITECTURA DE COMPUTADORES I Práctica 3. Optimización de Código Departamento de Arquitectura y Tecnología de Computadores E.T.S. Ingeniería Informática Julio Ortega Lopera. Curso 2008/2009
Bibliografía • http://www.intel.com/design/PentiumIII/manuals/ • http://developer.intel.com/design/pentium4/manuals/index.htm • GERBER, R.:”The Software Optimization Cookbook. High Performance Recipes for the Intel Architecture”. Intel Press, 2002. • GERBER, R.; et al.:”The Software Optimization Cookbook. High Performance Recipes for the IA-32 Platforms”. Intel Press, 2006. • FOG, A.:”How to Optimize for the Pentium family of microprocessors”, http://cr.yp.to/2005-590/fog.pdf, 2004. Arquitectura de Computadores I. Práctica 3
Herramientas para realizar la Práctica En esta práctica se puede utilizar el compilador de C que cada uno desee. No obstante el que se ha considerado para plantear los ejercicios a realizar es el GCC para DOS/WINDOWS: www.delorie.com/djgpp Se puede utilizar el compilador de C de Intel para compilar y la herramienta VTune para visualizar los distintos códigos Compilador: Intel C++ Herramienta de análisis: VTune Versiones de evaluación a partir de la página: developer.intel.com/design/index.htm (a partir de la opción “Intel Software Evaluation Center”) Arquitectura de Computadores I. Práctica 3
Índice • Cuestiones generales sobre optimización • Optimización de la Captación de Instrucciones • Optimización de la Decodificación y el Renombrado de Registros • Optimización de la Ejecución • Optimización del Acceso a Memoria Principal y Cache • Optimización de Saltos • Optimización de Bucles • Realización de los Ejercicios Prácticos Arquitectura de Computadores I. Práctica 3
CUESTIONES GENERALES SOBRE OPTIMIZACIÓN (I) • Explotando las características de la microarquitectura de un procesador recientemente aparecido, un programador puede aprovechar nuevas propiedades que ofrece dicho procesador antes de que las mismas se hayan incorporado en un compilador o en una biblioteca y estén disponibles en el mercado (con el coste correspondiente): • Ejemplo: • La instrucción CMOV se incorpora al repertorio de instrucciones x86 con el Pentium Pro, en 1995. • El repertorio SSE (Streaming SIMD Extension) aparece con el Pentium III en 1999. • Sin embargo, hasta la versión de 2003 del Visual C++ .NET no se aprovechaba ni la instrucción CMOV, ni las instrucciones SSE para generar código eficiente Arquitectura de Computadores I. Práctica 3
CUESTIONES GENERALES SOBRE OPTIMIZACIÓN (II) • Usualmente la optimización de una aplicación se realiza al final del proceso, si queda tiempo. Esperar al final para optimizar dificulta el proceso de optimización. • Es un error escribir la aplicación sin tener en cuenta la arquitectura o arquitecturas en las que se va a ejecutar. • No es correcto optimizar eliminando propiedades y funciones (features) del código, en el caso de que no se satisfagan las restricciones de tiempo. • La optimización debe realizarse durante el proceso de desarrollo, utilizando las características de optimización del compilador (no es adecuado desactivar estas opciones para facilitar la depuración del código). • Cuando se optimiza código es importante analizar donde se encuentran los cuellos de botella. El cuello de botella más estrecho es el que al final determina las prestaciones y es el que debe evitarse en primer lugar. • Se puede optimizar sin tener que acceder al nivel del lenguaje ensamblador (en algunos casos sí). Arquitectura de Computadores I. Práctica 3
CUESTIONES GENERALES SOBRE OPTIMIZACIÓN (III) Una Clasificación de las Optimizaciones Un Compilador puede ejecutarse utilizando diversas opciones de optimización. Por ejemplo el compilador de c gcc dispone de las opciones –O1, -O2, -O3, -Os que proporcionan códigos con distintas opciones de optimización. Es posible encontrar una descripción de las distintas alternativas de optimización en: http://gcc.gnu.org/onlinedocs/ Arquitectura de Computadores I. Práctica 3
Índice • Cuestiones generales sobre optimización • Optimización de la Captación de Instrucciones • Optimización de la Decodificación y el Renombrado de Registros • Optimización de la Ejecución • Optimización del Acceso a Memoria Principal y Cache • Optimización de Saltos • Optimización de Bucles • Realización de los Ejercicios Prácticos Arquitectura de Computadores I. Práctica 3
VariosCiclos Captación Decodificación RenombradoEscritura ROBEmisión Retirar(3uops/ciclo) IF1 IF2 IF3 ID1 ID2 RAT ROB Emit(RS) EX RET1 RET2 Etapas del Cauce en la microarquitectura P6 Arquitectura de Computadores I. Práctica 3
32 Bytes IF1 IPSiguiente De la cache deInstrucciones 16 Bytes IF2 PredicciónDinámica de Saltos 16 Bytes IF3 16 Bytes Secuenciador deMicroinstrucciones ID1 D0 D1 D2 6 x 118 Bytes PredicciónEstática de Saltos ID2 3 x 118 Bytes 3 x 118 Bytes RAT Al ROB Captación y Decodificación en la microarq. P6 Arquitectura de Computadores I. Práctica 3
Optimización de la Captación en P6 (I) Los bloques ifetch (como máximo de 16 bytes, no alineados) pasan desde del buffer de instrucciones captadas (32 bytes como máximo, alineados) a los decodificadores. Los bloques ifetch comienzan en el inicio de una instrucción y tienen 16 bytes, salvo que haya una instrucción de salto para la que se tenga historia (predicción dinámica Si se produce salto, pueden pasar dos ciclos hasta que se capte la instrucción siguiente (si la instrucción cruza un límite de 16 bytes y hacen falta dos accesos): es beneficioso que haya más instrucciones en el bloque ifetch que puedan pasar a decodificarse mientras se espera la instrucción correspondiente. Saber la forma en que se van delimitando los ifetch (sobre todo después de un salto) permite determinar qué instrucciones van a ir pasando a decodificarse y se puede mejorar el rendimiento de la decodificación (como se verá) Hasta que no se ha terminado de decodificar un ifetch no se inicia el siguiente. Arquitectura de Computadores I. Práctica 3
Optimización de la Captación en P6 (II) En la siguiente tabla se muestran las reglas que permiten conocer el alineamiento del primer bloque ifetch después de un salto. Arquitectura de Computadores I. Práctica 3
Optimización de la Captación en P6 (III) Ejemplo Primer IFETCH: 1000h –1010h (sin incluir) (2 ciclos en decodificar) Segundo IFETCH: 1007h –1017h (sin incluir) (1 ciclo en decodificar) Tercer IFETCH: 1017h – 1022h (inclusive) (3 ciclos en decodificar) La primera iteración del bucle LL necesita 5 ciclos para decodificarse El primer bloque ifetch después del salto empezará en la instrucción LL ya que el último bloque ifetch tiene una alineación de 16 bytes y tres grupos de decodific. en 1020h (desde 1005h a 1015h) El siguiente bloque ifetch empieza en 1011h y termina antes de la 1021h y el último bloque empieza en 1021h hasta el final (y no incluye límites de 16 bytes) Se necesitan 7 ciclos para decodificar la segunda iteración y el bloque ifetch de la siguiente iteración empieza en un límite de 16 bytes que incluya a la dirección de salto (1000h) Las iteraciones impares necesitan 5 ciclos y las pares 7 ciclos para decodificarse
Índice • Cuestiones generales sobre optimización • Optimización de la Captación de Instrucciones • Optimización de la Decodificación y el Renombrado de Registros • Optimización de la Ejecución • Optimización del Acceso a Memoria Principal y Cache • Optimización de Saltos • Optimización de Bucles • Realización de los Ejercicios Prácticos Arquitectura de Computadores I. Práctica 3
movl _MEM1, %ebx 1 uop (D0) incl %ebx 1 uop (D1) addl _MEM2, %eax 2 uops (D0) addl %eax, _MEM3 4 uops (D0) Optimización de la Decodificación (I) Los decodificadores pueden manejar tres instrucciones por ciclo pero sólo si se reúnen una serie de condiciones que se resumen a continuación: La primera instrucción, decodificada en D0 no puede generar más de 4 uops en un sólo ciclo de reloj, y la segunda y tercera instrucción no deben generar más de 1 uop cada una. La segunda y tercera instrucción no debe tener más de 8 bytes cada una. Las instrucciones deben estar contenidas en el mismo bloque de ifetch de 16 bytes. addl %eax, _MEM3 4 uops (D0) movl _MEM1, %ebx 1 uop (D1) incl %ebx 1 uop (D2) addl _MEM2, %eax 2 uops (D0) Se gana un ciclo en la decodificación (3 2) Microarquitectura P6 Arquitectura de Computadores I. Práctica 3
Optimización de la Decodificación (II) • Los prefijos que tienen ciertas instrucciones también pueden ocasionar pérdidas de ciclos en los decodificadores. • Prefijo de tamaño de operando cuando se tiene un operando de 16 bits en un entorno de 32 o viceversa (Operand-size override prefix, 66h). • Prefijo de tamaño de dirección (Address-size override prefix, 67h). • Un prefijo produce un ciclo de penalización (si se utiliza más de un prefijo, habría una penalización de un ciclo por prefijo) si: • El prefijo de tamaño de operando se utiliza con un operando inmediato • El prefijo de ajuste de dirección se utiliza con una dirección que incluye un offset. movw $0x77, _mem movl $0x77,%eax movw %ax,_mem Prefijo 66h en el modo de 32 bits (almacena un dato de 16 bits en memoria) Se ahorra un ciclo addw _mem,%ax movl %edx,(%ax) movl %edx,_mem(%ax) Prefijo 67h (almacena un dato en memoria utilizando un offset) Microarquitectura P6 Arquitectura de Computadores I. Práctica 3
Optimización de la Decodificación (III) En la microarquitectura NetBurst el decodificador puede generarde 1 a 4 uops por instrucción y ciclo. Las instrucciones que necesitan más de 4 uops se envían a una memoria ROM de microcódigo. En este caso, la instrucción puede tardar más de un ciclo en decodificarse. Las instrucciones que tienen más de un prefijo necesitan un ciclo de decodificación por prefijo. El tiempo de decodificación no es importante en el caso de bucles que quepan en la cache de traza. Si las uoperaciones correspondientes a una instrucción no están en la cache de traza, éstas pasan a las etapas de ejecución desde el decodificador y, en este caso, la velocidad de decodificación sí es relevante (las instrucciones pasan al decodificador desde la cache L2). Las trazas de código que tardan en decodificarse más que en ejecutarse son las que suelen incluirse en la cache de traza (de forma automática). Microarquitectura NetBurst Arquitectura de Computadores I. Práctica 3
Cache de Traza en NetBurst (I) En el Pentium 4 las instrucciones se introducen en una cache de traza tras haber sido decodificadas en uoperaciones (en lugar de almacenarse las instrucciones en una cache L1 para instrucciones, en la cache de traza se almacenan las trazas de las uoperaciones consecutivas correspondientes). Existe una cache L2 para código y datos de 256 KB como mínimo, con un bus de acceso de 256 bits. La cache de traza está organizada en 2048 líneas con espacio para 6 uops cada una y con correspondencia asociativa por conjuntos de 4 vías. En el espacio disponible para cada uop hay 16 bits de datos: si una uop necesita más bits para los datos, ocupa más espacio (espacio de varias uops). Hay que tener en cuenta que, ninguna uop depende de más de dos operandos de entrada. Por ejemplo: CMOVcc puede tener más de dos operandos y se divide en dos uops MOV [ESI+EDI], AX también da lugar a dos uops MOV EAX,[MEM1] necesita dos espacios de la cache (la dirección puede tener más de 16 bits) Arquitectura de Computadores I. Práctica 3
Cache de Traza en NetBurst (II) Las uops pueden extraerse de la cache a una velocidad que corresponde a 1 línea de cache cada dos ciclos: esto supone unas 3 uops por ciclo (aproximadamente). Es conveniente que ninguna uop que ocupe dos espacios cruce los límites de 6 espacios que correspoden al final/comienzo de una línea. Por ejemplo, se podrían reordenar de forma que haya un número par (considerando al 0 como número par) de uops que ocupan un espacio entre cada dos instrucciones de doble espacio. MOV EAX, [MEM1] (1 uop, 2 esp.) ADD EAX, 1 (1 uop, 1 esp.) MOV EBX,[MEM2] (1 uop, 2 esp.) MOV [MEM3],EAX (1 uop, 2 esp.) ADD EBX,1 (1 uop, 1 esp.) MOV EAX, [MEM1] (1 uop, 2 esp.) MOV EBX,[MEM2] (1 uop, 2 esp.) ADD EAX, 1 (1 uop, 1 esp.) ADD EBX,1 (1 uop, 1 esp.) MOV [MEM3],EAX (1 uop, 2 esp.) Arquitectura de Computadores I. Práctica 3
Cache de Traza en NetBurst (IV) • Líneas generales para mejorar las prestaciones de la cache de traza: • Utilizar instrucciones que generen pocas uops • Utilizar datos inmediatos entre -215 y 215, si es posible • Evitar direccionamientos directos con direcciones de 32 bits • Evitar un número impar de uops que necesitan un solo espacio entre uops de dos espacios • Intentar sustituir las instrucciones de salto condicional por instrucciones de movimiento condicional (si eso no implica costos excesivos por otro lado) Arquitectura de Computadores I. Práctica 3
Optimización del Renombrado (I) • Para la optimización de la etapa de renombrado en el RAT,lo ideal es que, en un ciclo, no se introduzcan en el RAT grupos de uoperaciones que lean más de dos registros del banco de registros. • Se pueden seguir ciertas recomendaciones: • Mantener las uoperaciones que leen el mismo registro lo más cerca posible para que sea más probable que entren a la vez en el RAT. • Mantener las uoperaciones que leen de registros diferentes lo más lejos posible para que no entren a la vez en el RAT. • Provocar renombrados de registros para evitar los ciclos perdidos en el acceso a los registros (si no se introducen muchas uoperaciones). Arquitectura de Computadores I. Práctica 3
MOV EAX, EBX SUB ECX, EAX INC EBX MOV [EAX], EDX ADD ESI, EBX ADD ESI, ECX Todas las instrucciones generan una uop. Las tres primeras pasan al RAT. Utilizan tres registros, pero como EAX se escribe antes, se le asignará una entrada en el ROB (se renombra) y se lee desde allí. Sólo se leen EBX y ECX. En la segunda tanda de tres uops se necesitan más de dos registros, pero como se ha escrito antes sobre EBX, y ECX sólo ESI y EDX deben leerse desde el fichero de registros Optimización del Renombrado (II) Si la lectura de un operando se hace desde el ROB (en lugar de utilizarse el Banco de Registros) no existen limitaciones para el número de lecturas (la limitación en el número de lecturas viene del número de puertos de lectura del banco de registros). Ninguna uop usa más de dos operandos (todas las instrucciones que usan más de dos registros dan lugar a dos o más uops) Lo ideal es que no pasen a la vez por el RAT uops que lean más de dos registros del fichero de registros: es difícil predecir qué uops pasan por el RAT en cada ciclo (un salto mal predicho descarta las instrucciones de la cola entre la unidad de decodificación y el ROB y habría que tener en cuenta el orden en que se generan las uops). Arquitectura de Computadores I. Práctica 3
Índice • Cuestiones generales sobre optimización • Optimización de la Captación de Instrucciones • Optimización de la Decodificación y el Renombrado de Registros • Optimización de la Ejecución • Optimización del Acceso a Memoria Principal y Cache • Optimización de Saltos • Optimización de Bucles • Realización de los Ejercicios Prácticos Arquitectura de Computadores I. Práctica 3
Optimización de la Ejecución: Unidades de ejecución Se podrían realizar cuatro sumas por ciclo pero hay que tener en cuenta que la cache de traza sólo emite tres uops por ciclo: esta velocidad de ejecución se puede conseguir si las instrucciones han estado en cola durante un cierto periodo de tiempo previo. Si todos los puertos estuviesen generando resultados a su máxima velocidad se podrían terminar 6 uops por ciclo (pero hay que tener en cuenta que las instrucciones se retiran a un ritmo de 3 uops por ciclo, como en la microarquitectura P6. La mayoría de las subunidades están segmentadas (la fp-div no está segmentada, y puede tardar entre 23 y 43 ciclos de reloj) Microarquitectura NetBurst Arquitectura de Computadores I. Práctica 3
Optimización de la Ejecución: Registros parciales Se puede producir un 'atasco' en el cauce debido al uso de un registro parcial si se lee un registro de mayor tamaño después de una escritura en un registro parcial Para evitar ese tipo de atascos se tendría que borrar el registro mayor con XOR o SUB antes de escribir sobre el registro parcial. Borrar el registro mayor con MOV no evita el atasco. mov eax, 0 mov ax, mem16 add ecx, eax xor eax, eax mov ax, mem16 add ecx, eax mov ah,cl mov eax,ecx mov al,dl shl eax,8 mov mem, eax and edx,0xff Con atasco Sin atasco Arquitectura de Computadores I. Práctica 3
Optimización de la Ejecución: Bit de Estado Una escritura en alguno (no en todos) de los flags de estado del registro EFLAGS precede a una lectura tanto de los flags modificados como no modificados (no hay problema si se leen sólo los modificados o sólo los no modificados). sahf// almacena el registro ah en el registro de flags excepto OF jg label// jg lee el flag OF junto con los SF y ZF sahf // no hay atasco instrucciones que no leen los flags jg label Instrucciones como INC y DEC que no actualizan todos los flags de estado (no actualizan el flag de acarreo, CF) pueden provocar atascos: En estos casos es mejor utilizar ADD o SUB (Ojo en el control de bucles!!) Arquitectura de Computadores I. Práctica 3
Optimización de la Ejecución: Desenrollado de Bucles Utilizar el desenrollado de bucles para romper secuencias de instrucciones dependientes intercalando otras instrucciones. float dot-product(float *a, float *b) { int i; float tmp=0.0; for (i=0; i<ARR; i++) { tmp += a[i]*b[i]; } ret tmp; } float dot-product(float *a, float *b) { int i; float tmp0=0.0; float tmp1=0.0; float tmp2=0.0; float tmp3=0.0; for (i=0; i<ARR; i+=4) { tmp0 += a[i]*b[i]; tmp1 += a[i+1]*b[i+1]; tmp2 += a[i+2]*b[i+2]; tmp3 += a[i+3]*b[i+3]; } ret tmp0+tmp1+tmp2+tmp3; } • El desenrollado: • Reduce el número de saltos • Aumenta la oportunidad de encontrar instrucciones independientes • Facilita la posibilidad de insertar instrucciones para ocultar las latencias. • La contrapartida es que aumenta el tamaño de los códigos. for (i=0;i<100;i++) if ((i%2) = = 0) a[i]=x; else a[i]=y; for (i=0;i<100;i+=2) { a[i]=x; a[i+1]=y; } Ejemplo Arquitectura de Computadores I. Práctica 3
Optimización de la Ejecución: Unidades Funcionales de la Microarquitectura Es importante tener en cuenta las unidades funcionales de que dispone la microarquitectura para utilizar las instrucciones de la forma más eficaz. Así: - La división es una operación muy costosa y por lo tanto habría que evitarla (por ejemplo, utilizando desplazamientos) o reducir su número. temp=1/y; for (i=0;i<100;i++) a[i]=a[i]*temp; for (i=0;i<100;i++) a[i]=a[i]/y; - A veces es más rápido utilizar desplazamientos y sumas para realizar una multiplicación por una constante entera que utilizar la instrucción IMUL. lea ecx,[eax+eax] lea eax,[ecx+eax*8] imul eax,10 Arquitectura de Computadores I. Práctica 3
Optimización de la Ejecución: No utilizar código ambiguo • No utilizar código ambiguo ya que si los compiladores no pueden resolver los punteros, tampoco pueden realizar ciertas optimizaciones, asignar variables durante la compilación, realizar cargas de memoria mientras que un almacenamiento está en marcha. • Para evitar esto: Utilizar variables locales en lugar de punteros, utilizar variables globales si no se pueden utilizar las locales, y poner las instrucciones de almacenamiento después o bastante antes de las de carga de memoria. • No obstante, si no se utilizan punteros el código es más dependiente de la máquina, y a veces las ventajas de no utilizarlos no compensa. • int j; int j; • void mars (int *v) { void mars(int v) { • j=7.0; j=7.0; • *v=15; v=15; • j/=7; j/=7; • ........ ......... • } } • En el código no optimizado, el compilador no puede asumir que *v no apunt a j. Sin embargo en el código optimizado, el compilador puede hacer directamente j=1.0. Arquitectura de Computadores I. Práctica 3
Índice • Cuestiones generales sobre optimización • Optimización de la Captación de Instrucciones • Optimización de la Decodificación y el Renombrado de Registros • Optimización de la Ejecución • Optimización del Acceso a Memoria Principal y Cache • Optimización de Saltos • Optimización de Bucles • Realización de los Ejercicios Prácticos Arquitectura de Computadores I. Práctica 3
Optimización del Acceso a Memoria y Cache: Alineación de Datos (II) En el Pentium Pro, Pentium II y Pentium III, el acceso a los datos no alineados supone un costo adicional de entre 6 y 12 ciclos cuando se cruza el límite de la línea de cache. Los operandos menores de 16 bytes que no crucen una línea de cache no dan lugar a penalización. Es posible controlar, desde un programa escrito en un lenguaje de alto nivel como C, la alineación de los datos que utiliza dicho programa y con ello evitar la penalización que pueda producirse por una falta de alineación. N : Tamaño del vectorBOUND : 0x20 = 00000000000000000000000000100000BOUND – 1 : 0x1F = 00000000000000000000000000011111~(BOUND-1): 0xFFE0 = 11111111111111111111111111100000 BOUND=32 si se quiere que esté alineado con líneas de cache de 32 bytes Arquitectura de Computadores I. Práctica 3
Optimización del Acceso a Memoria y Cache: Alineación de Datos (II) Cuando el código accede a un array de forma no secuencial es conveniente ajustar y alinearla estructura de forma que ocupen el mínimo número de líneas de cache. Ejemplo: En el código del ejemplo se accede a la primera y a la última estructura de un array de 5 estructuras de 28 bytes. Cuando se accede a una estructura se producen dos faltas de acceso a cache en lugar de una (en el caso de estas dos estructuras, tal y como se consideran situadas en el ejemplo) Código originalCódigo Optimizado struct { struct{ int a[7]; int a[7]; } s[5]; int pad; .......... } s[5]; for (i=0;i<7;i++) s[1].val[i]+=s[5].val[i]; p=(struct s*) malloc (sizeof (struct s)*5+32); new_p=(p+31)&(-31); Se añaden variables ficticias para llenar 32 bytes y se alinean las estructuras para que empiecen en una línea de cache. Arquitectura de Computadores I. Práctica 3
Optimización del Acceso a Memoria y Cache: Colisiones en Cache Puesto que la cache es asociativa por conjuntos de cuatro vías en el Pentium II y Pentium III y las líneas son de 32 bytes, los bits 5 a 11 de la dirección física de memoria (0 es el bit menos significativo) indican el conjunto en el que se introduce la línea correspondiente. Para conocer si dos líneas se almacenan en cache en el mismo conjunto, se toman dos direcciones, una de cada línea y se hacen igual a 0 los 5 bits menos significativos de ambas. Si la diferencia de las nuevas direcciones es múltiplo de 4096, las líneas a las que pertenecen esas direcciones van al mismo conjunto. El ejemplo siguiente ilustra un procedimiento para asegurar que dos zonas de datos se asignan a distintos conjuntos (y no colisionen en cache): int *tempA, *tempB; ..................... pA= (int *) malloc (sizeof(int)*N + 31); tempA = (int *)(((int)pA+31)&~(31)); tempB = (int *)((((int)pA+31)&~(31))+4096+32); Los punteros tempA y tempB están apuntando a zonas de memoria que empiezan en posiciones que son múltiplos de 32 y que no se asignarían al mismo conjunto de cache. Arquitectura de Computadores I. Práctica 3
Optimización del Acceso a Memoria y Cache: Almacenamiento y Referencia a los datos (I) La forma en que se declaren los arrays determina la forma en que se almacenan en memoria. Interesa declararlos según se vaya a realizar el acceso. Ejemplos: Formas óptimas de declaración de variables según el tipo de acceso a los datos struct { struct { int a[500]; int a; int b[500]; int b; } s; } s[500]; ....... ............ for (i=0; i<500; i++) for (i=0;i<500;i++) s.a[i]=2*s.a[i]; { ....... s[i].a+=5; for (i=0;i<500;i++) s[i].b+=3; s.b[i]=3*s.b[i]; } Arquitectura de Computadores I. Práctica 3
Optimización del Acceso a Memoria y Cache: Almacenamiento y Referencia a los datos (II) Intercambiar los bucles para cambiar la forma de acceder a los datos según los almacena el compilador, y para aprovechar la localidad Ejemplo: Código Original for (j=0; j<4; j++) for (i=0;i<4;i++) a[i][j]=2*a[i][j]; Código Optimizado para C (se almacena la matriz por filas) for (i=0; i<4; i++) for (j=0;j<4;j++) a[i][j]=2*a[i][j]; Arquitectura de Computadores I. Práctica 3
Optimización del Acceso a Memoria y Cache: Mejora del rendimiento del acceso especulativo • Los 'atascos' (stalls) por acceso a la memoria (load adelanta a store, especulativo) se producen cuando: • Hay una carga (load) 'larga' que sigue a un almacenamiento (store) 'pequeño' alineados en la misma dirección o en rangos de direcciones solapadas. • mov word ptr [ebp],0x10 • mov ecx, dword ptr [ebp] • Una carga (load) 'pequeña' sigue a un almacenamiento (store) 'largo' en direcciones diferentes aunque solapadas (si están alineadas en la misma dirección no hay problemas). • mov dword ptr [ebp-1], eax • mov ecx,word ptr [ebp] • Datos del mismo tamaño se almacenan y luego se cargan desde direcciones solapadas que no están alineadas. • mov dword ptr [ebp-1], eax • mov eax, dword ptr [ebp] • Para evitarlos: Utilizar datos del mismo tamaño y direcciones alineadas y poner los loads tan lejos como sea posible de los stores a la misma área de memoria Arquitectura de Computadores I. Práctica 3
Optimización del Acceso a Memoria y Cache: Blocking for (jj=0;jj<N;jj=jj+B) for (kk=0;kk<N;kk=kk+B) for (i=0;i<N;i=i+1) for (j=jj;j<min(jj+B-1,N);j=j+1) { r=0; for (k=kk; k<min(kk+B-1,N);k=k+1) { r= r + y[i][k]*z[k][j]; } ; x[i][j] = x[i][j] + r; }; for (i=0;i<N;i=i+1) for (j=0;j<N;j=j+1) { r=0; for (k=0; k<N;k=k+1) { r= r + y[i][k]*z[k][j]; } ; x[i][j] = r; }; Arquitectura de Computadores I. Práctica 3
Optimización del Acceso a Memoria y Cache: Pre-captación (I) Pentium 4: Cache L1 de datos con líneas de 64 bytes (128 líneas * 64 bytes/línea = 8 KBytes) , asociativa por conjuntos de 4 vías. Cache L2 unificada, con líneas de 128 bytes (2K líneas * 128 bytes = 256 Kbytes), asociativa por conjuntos de 8 vías. El procesador, mediante las correspondientes instrucciones de prefetch, carga zonas de memoria en cache antes de que se soliciten (cuando hay ancho de banda disponible). Hay cuatro tipos de instrucciones de prefetch: Arquitectura de Computadores I. Práctica 3
Optimización del Acceso a Memoria y Cache: Pre-captación (II) Una instrucción de prefetch carga una línea entera de cache (en el Pentium 4 sólo sería necesario captar 1 byte de cada 64 byte) El aspecto crucial al realizar precaptación es la anticipación con la que se pre-captan los datos. En muchos casos es necesario aplicar una estrategia de prueba y error. Además, la anticipación óptima puede cambiar según las características del computador (menos portabilidad en el código). For (i=0; i<1000; i++) { x=function(matriz[i]); _mm_prefetch(matriz[i+16],_MM_HINT_T0); } • En el ejemplo se precapta el dato necesario para la iteración situada a 16 iteraciones (en el futuro) • En un prefetch no se generan faltas de memoria (es seguro precaptar más allá de los límites del array Arquitectura de Computadores I. Práctica 3
Índice • Cuestiones generales sobre optimización • Optimización de la Captación de Instrucciones • Optimización de la Decodificación y el Renombrado de Registros • Optimización de la Ejecución • Optimización del Acceso a Memoria Principal y Cache • Optimización de Saltos • Optimización de Bucles • Realización de los Ejercicios Prácticos Arquitectura de Computadores I. Práctica 3
OPTIMIZACIÓN DE SALTOS Cada una de las condiciones separadas por && se evalúa mediante una instrucción de salto distinta. Si las variables pueden ser 1 ó 0 con la misma probabilidad, la posibilidad de predecir esas instrucciones de salto no es muy elevada. if (t1==0 && t2==0 && t3==0) Si se utiliza un único salto, la probabilidad de 1 es de 0.125 y la de 0 de 0.875 y la posibilidad de hacer una buena predicción aumenta. if ((t1 | t2 | t3)==0) // if ((t1 | t2 | t3)==0) { t4=1}; //t1 -> edi t2 -> ebx t3 -> ebp t4 -> eax mov ecx, 1 or edi, ebx or edi, ebp cmove eax, ecx Mediante la instrucción de movimiento condicional se pueden evitar los daltos Arquitectura de Computadores I. Práctica 3
OPTIMIZACIÓN DE SALTOS: MEJORA DE LA PREDICCIÓN (I) Microarquitectura P6 Predicción Dinámica (36 bits) Dada una secuencia de saltos/no saltos para una instrucción de salto, si toda subsecuencia de cuatro bits (indicando cada bit si se produce salto o no) va seguida del mismo bit, la secuencia es predecible (tras el correspondiente número de ciclos de aprendizaje) Secuencia predecible:1000100010001000 Secuencia no predecible:000001000001 • Cuando no hay historia para una instrucción de salto, esto es, la primera vez que se ejecuta, se utiliza el siguiente procedimiento de predicción estática: • Si la dirección de salto no es relativa al contador de programa IP: Predice 'Saltar' si el salto es un 'return', y 'No Saltar' en caso contrario. • Si la dirección de salto es relativa a IP: Predice 'Saltar' si el salto es hacia atrás (situación análoga a los bucles), y 'No Saltar' si el salto es hacia delante. Arquitectura de Computadores I. Práctica 3
OPTIMIZACIÓN DE SALTOS: MEJORA DE LA PREDICCIÓN (II) • En algunos casos, los bucles se pueden organizar para que den lugar a secuencias predecibles por el esquema de predicción dinámica: • Si un bucle se ejecuta 20 veces no se predice correctamente la última iteración. Para evitar esto se puede utilizar dos bucles anidados de 4 y 5 iteraciones respectivamente, o desenrollar el bucle por cuatro, para que sólo haya 5 iteraciones. • Si un salto que no tiene ninguna historia almacenada en el BTB se utiliza la predicción estática, que predice los saltos hacia delante como no tomados. Se pueden mejorar las prestaciones del procedimiento si se situa el código más frecuente después del salto condicional hacia delante. Código Ensamblador Original comp a, 5 je L1 Código Infrecuente jmp L2 L1: Código Frecuente L2: Código Ensamblador Mejorado comp a, 5 jne L1 Código Frecuente jmp L2 L1: Código Infrecuente L2: Arquitectura de Computadores I. Práctica 3
OPTIMIZACIÓN DE SALTOS: REDUCCIÓN DE SALTOS (I) Se puede reducir el número de saltos de un programa reorganizando las alternativas en las sentencias switch,en el caso de que alguna opción se ejecute mucho más que las otras (más del 50% de las veces, por ejemplo). Ciertos compiladores que utilizan información de perfiles de ejecución del programa son capaces de realizar esta reorganización (Se recomienda utilizarla si la sentencia switch se implementa como una búsqueda binaria en lugar de una tabla de salto). Código original switch (i) { case 16: Bloque16 break; case 22: Bloque22 break; case 33: Bloque33 break; } Código Optimizado if (i==33) { Bloque33 } else switch (i) { case 16: Bloque16 break; case 22: Bloque22 break; } Arquitectura de Computadores I. Práctica 3
OPTIMIZACIÓN DE SALTOS: REDUCCIÓN DE SALTOS (II) CMOVcc hace la transferencia de información si se cumple la condición indicada en cc test ecx,ecx test ecx,ecx jne 1h cmoveq eax, ebx mov eax,ebx 1h: FCMOVcc es similar a CMOVcc pero utiliza operandos en coma flotante Arquitectura de Computadores I. Práctica 3
OPTIMIZACIÓN DE SALTOS: REDUCCIÓN DE SALTOS (III) La instrucción SETcc es otro ejemplo de instrucción con predicado que puede permitir reducir el número de instrucciones de salto. ebx= (A<B) ? C1 : C2; [ Si (A<B) es cierto EBX se carga con C1 y si no con C2 ] Código Original cmp A,B jge L30 mov ebx,C1 jmp L31 L30: mov ebx,C2 L31: Código Optimizado Explicación: xor ebx,ebx Si A>=B, setge hace BL=1; DEC hace EBX=0; (EBX and C1-C2)=0; EBX + C2 = C2 cmp A,B setge bl Si A<B, setge hace BL=0; DEC hace EBX=0xFFFFFFFF; (EBX and C1-C2)= C1-C2; dec ebx EBX+C2=C1 and ebx, (C1-C2) add ebx, C2 Arquitectura de Computadores I. Práctica 3
Índice • Cuestiones generales sobre optimización • Optimización de la Captación de Instrucciones • Optimización de la Decodificación y el Renombrado de Registros • Optimización de la Ejecución • Optimización del Acceso a Memoria Principal y Cache • Optimización de Saltos • Optimización de Bucles • Realización de los Ejercicios Prácticos Arquitectura de Computadores I. Práctica 3
OPTIMIZACIÓN DE BUCLES (I) • En un programa, a menudo, la mayor parte del tiempo se pasa en uno de los bucles del mismo. La forma más directa de mejorar la velocidad consiste en optimizar cuidadosamente el bucle que más tiempo consuma utilizando el lenguaje ensamblador. • En los ejemplos que se considerarán a continuación, se asume que los datos están en la cache de nivel 1. Si la velocidad está limitada por los fallos de cache habría que concentrarse primero en distribuir los datos para disminuir los fallos. • Como ejemplo se tomará un procedimiento sencillo en C: • void Cambiosigno (int *A, int *B, int N) { • int i; • for (i=0; i<N; i++) B[i]= -A[i] ;} • Para optimizar el código abría que tener en cuenta: • La alineación de los bloques de captación (ifetch) y la decodificación • Los atascos (stalls) en la lectura de registros y las características de ejecución de uops • El desenrollado de bucles Arquitectura de Computadores I. Práctica 3
OPTIMIZACIÓN DE BUCLES (II) .file "cambiosign.c" gcc2_compiled.: ___gnu_compiled_c: .text .p2align 2 .globl _changesign _changesign: pushl %ebp movl %esp,%ebp subl $16,%esp pushl %esi pushl %edi movl 16(%ebp),%ecx jecxz L2 movl 8(%ebp),%esi movl 12(%ebp),%edi .p2align 4,,7 cld L1: lodsl negl %eax stosl loop L1 L2: leal -24(%ebp),%esp popl %edi popl %esi movl %ebp,%esp popl %ebp ret Este código puede mejorarse más, tratando de evitar las instrucciones que generan muchas uops, como por ejemplo LOOP, LODS, y STOSD. A continuación se considerarán distintas mejoras del código (zona representada dentro del cuadro de línea discontinua) teniendo en cuenta la microarquitectura del procesador y utilizando el desenrollado de bucles.
OPTIMIZACIÓN DE BUCLES (III) A continuación se mostrarán algunas mejoras de este código considerando distintos aspectos de la microarquitectura del procesador: Decodificación de instrucciones. En la versión mejorada del programa ensamblador hay una instrucción que genera 2 uops (MOV [EDI],EAX) y debe ir al decodificador D0. En cada iteración del bucle existen tres grupos de decodificación (se podrían decodificar en tres ciclos). Límites de los bloques de 16 bytes de instrucciones que se captan. Una forma de mejorar la velocidad es conseguir que las instrucciones del bucle estén alineadas en el menor número posible de estos bloques Atascos producidos por las lecturas de registros (posibles riesgos de tipo RAW) Análisis de las uops que van a cada puerto de ejecución. Hay que evitar las colisiones en la medida de lo posible. Retirada de instrucciones del ROB. En cada ciclo se pueden retirar como mucho 3 uops. Arquitectura de Computadores I. Práctica 3