Organización del Computador 1 Facultad de Ciencias Exactas y Naturales Universidad de Buenos Aires 26 de Octubre de 2010 Clase Práctica Memoria Caché ¿Qué es la memoria caché? En el sentido general, un caché es un espacio de almacenamiento de velocidad alta donde se guarda una copia de una parte de los datos que se encuentran en un segundo medio de almacenamiento de velocidad más reducida. En caso que nos interesa, el caché de un CPU, se trata de una memoria de muy alta velocidad que por lo general se encuentra integrada en el chip del CPU y guarda una parte de los datos de la memoria RAM para permitir acceder a ellos más rápidamente. La palabra proviene del francés “cache” (pronunciado cash) que significa “escondite”. Los cachés funcionan siempre en el contexto de una jerarquía de memoria, es decir, una organización de los dispositivos de almacenamiento en la que existen velocidades y capacidades distintas. Así, solo tiene sentido utilizar dispositivos más rápidos como caché de otros más lentos. En la figura se observa una comparación del tiempo necesario para acceder a un dato en memoria desde el CPU según el dispositivo en que se encuentra alojado. Las cachés L1, L2 y L3 que se encuentran en un procesador moderno se organizan en una estructura jerárquica: una caché L3 guarda datos de la RAM, una L2 guarda datos de la L3, una L1 guarda datos de la L2 y a su vez el CPU solo accede a datos que se encuentran en la caché L1. Antes de accederlos, será necesario cargar todos Tiempo de Acceso (ns) 120 100 80 60 40 20 0 Registro L1 L2 L3 RAM los niveles de caché. Como se observa en el gráfico, si el tiempo para acceder a un registro es de un ciclo de reloj, un acceso a memoria RAM tiene un costo inmenso y el tiempo de espera requerido por esta operación no puede ser utilizado para hacer cómputos: es un desperdicio importante de potencia computacional. Las cachés funcionan por lo general bajo la hipótesis de localidad de referencia de varios tipos, en particular: • localidad espacial: si se utiliza un dato en memoria en un instante dado, es muy probable que en los instantes siguientes se utilice algún dato en una dirección cercana • localidad temporal: si se utiliza un dato en memoria en un instante dado, es muy probable que en los instantes siguientes se vuelva a utilizar Características La característica primordial de una memoria caché es su función de mapeo. Esta función establece una o varias posiciones de caché en las que puede almacenarse una copia de una posición de RAM dada. Dado que el tamaño de la caché es siempre muy inferior al de la RAM, dicha función nunca puede ser inyectiva: necesariamente varias posiciones de la RAM serán almacenadas en la misma posición de la caché (aunque no simultáneamente!). Por esta razón, será necesario que al almacenar un dato en caché, se etiquete la posición de la caché indicando el origen (la posición en RAM) correspondiente al dato en cuestión. Esta etiqueta se denomina tag, y permite identificar cual de las múltiples posiciones de memoria que van a parar a una línea dada de la caché se encuentra allí en un momento determinado. Las memorias caché se organizan y operan (cargan y descargan) en líneas, a diferencia de una memoria convencional que se opera de a palabras. A su vez, dentro de cada línea, cada unidad direccionable tiene un índice dentro de la misma. Tags Palabras por línea Líneas 0 Índices 1 2 3 Tipos de caché Si bien puede diseñarse una función de mapeo de cualquier característica, en la materia nos interesaremos en una familia particular que es la más utilizada en la práctica. Este tipo de función de mapeo se llama “asociativa por conjuntos de N vías” y divide a las líneas de caché en conjuntos de N líneas. Así, si tuviéramos una caché asociativa por conjuntos de 2 vías, las 2 primeras líneas de la caché conformarían el conjunto 0, las 2 segundas el conjunto 1, y así sucesivamente. La función de mapeo se construye entonces de la siguiente forma: Dirección de memoria tag set (o “line”) index Así, los bits menos significativos denotan el índice, los siguientes el número de conjunto y los restantes el tag. La cantidad de bits de cada campo corresponde a la cantidad de valores posibles que pueden adoptar según las cantidades propias de la caché (como se observa en la figura anterior). Además, es de destacar que al cargarse la caché de a líneas, cuando se cargue una línea desde memoria, un bloque contiguo de RAM ocupará una línea en caché. Por esta razón, no es necesario tener un tag para cada posición dentro de cada línea de caché, sino que es suficiente con uno por línea: todas las posiciones cargadas en una línea en un momento dado comparten el mismo tag (los bits más significativos de su dirección). Una memoria caché asociativa por conjuntos de 1 vía se denomina “de mapeo directo”, mientras que si un único conjunto contiene a todas las líneas de la caché, se denomina “asociativa” a secas o “completamente asociativa”. Operatoria de la caché Al realizarse un acceso a memoria, la computadora busca en primer lugar si dicha posición se encuentra en caché. Para esto, evalúa la función de mapeo y obtiene un número de conjunto que corresponde a esa línea. Enseguida, accede a caché y busca el conjunto correspondiente, lo cual puede terminar de una de dos maneras: • Si alguna de las líneas del conjunto comparte el tag con la dirección que se intentó acceder, entonces el valor buscado se encuentra en caché y se puede devolver al CPU. Esta situación se denomina hit. • Si ninguna de las líneas del conjunto comparte el tag con la dirección que se intentó acceder, entonces el valor buscado no se encuentra en caché. Esta situación se denomina miss. En este caso, la computadora accede a memoria y busca la dirección pedida, cargándola en caché junto con el resto de las posiciones contiguas (se llena completamente la línea de caché). En caso de que todas las líneas del conjunto estuvieran ya ocupadas, se desalojará alguna según la política de desalojo establecida. A continuación se devuelve el valor pedido al CPU. El hit rate es la tasa de hits por sobre la cantidad de accesos totales a memoria. Se calcula como HR = hits / (hits + misses). El miss rate es análogo a éste, cambiando el numerador. Caché de escritura Además de las políticas detalladas antes, que se refieren a como se comporta el controlador de caché al momento de producirse una lectura, es necesario considerar también qué ocurre con las escrituras. Básicamente, frente a una escritura, deben considerarse dos aspectos del funcionamiento del controlador. En primer lugar, frente a una escritura, ¿debe cargarse la línea correspondiente de la caché? • En una caché write-allocate (asignación en escritura), cuando el CPU desea hacer una escritura a memoria, el controlador de caché carga la línea correspondiente. • En una caché write-no-allocate, (¿a que no adivinan?), no. En segunda instancia, si cuando se desea escribir a memoria, la posición involucrada se encuentra almacenada en caché1, ¿qué hace el controlador? • En una caché write-through (escritura inmediata), el controlador escribe los cambios tanto en la caché como en la memoria inmediatamente. • En una caché write-back (escritura demorada), el controlador escribe los cambios a caché pero no realiza la correspondiente escritura en memoria hasta tanto no se desaloje la línea en cuestión. Por lo general, las cachés write-back utilizan la política write-allocate. Esto se debe a que la caché write-back opera a partir de la suposición de que es probable que se produzcan múltiples escrituras en una misma posición, y por lo tanto resulta beneficioso esperar a que se completen todas antes de acceder a memoria para efectivizarlas. Por otro lado, las cachés write-through utilizan habitualmente la política write-no-allocate, puesto que no perciben ningún beneficio en tener la línea cargada a futuro (siempre accederán a memoria para realizar eventuales futuras escrituras). Políticas de desalojo Por último, las memorias caché con nivel de asociatividad mayor a uno comparten la característica de que una posición de memoria puede ocupar más de una posición en la memoria caché. Por lo tanto, de encontrarse llenos todos los espacios posibles que puede ocupar una posición de memoria que ingresa a caché, será necesario decidir cual de las alternativas será desalojada. Dicha decisión se toma según una política, entre las que se incluyen: • LFU (Least Frequently Used, menos frecuentemente usada), que como su nombre lo indica desalojará la línea que haya sido usada con menor frecuencia desde que fue cargada. A su vez, el cálculo de la frecuencia puede hacerse de diversas maneras que dependerán de la implementación del controlador de caché. • LRU (Least Recently Used, menos recientemente usada) que desalojará la línea que, entre las alternativas, haya sido usada por última vez hace más tiempo. • FIFO (First In First Out, el primero que entra sale primero) Para la implementación de estas políticas muchas veces es necesario espacio adicional en la caché (por ejemplo, para contabilizar la cantidad de veces que se usó una línea), que por lo general se ubica junto con la información de tag. 1 En una caché write-allocate esto ocurre siempre, porque siempre se carga la línea correspondiente! Ejercicio 1 Se dispone de una máquina con direcciones de 32 bits direccionando a byte, con palabras de 16 bits. La cache del CPU tiene 32KB utilizables (sin contar las posiciones reservadas para tags), en líneas de 8 bytes. Se desea utilizar una estrategia de correspondencia directa. 1. Indique la cantidad de bits de los campos tag, line e index. 2. Repita la tarea para una caché asociativa por conjuntos de 2 vías, otra asociativa por conjuntos de 8 vías y por último una caché totalmente asociativa. 3. Simule la siguiente secuencia de lecturas, indicando el estado de la caché, hits, misses, y el hit rate de toda la ejecución. Accesos: 0x70040, 0x70042, 0x70044, 0x70046, 0x70048, 0x8803c, 0x8803e, 0x88040, 0x70044, 0xb803f Solución 1. Cada línea de caché tiene 8 bytes, por lo tanto serán necesarios 3 bits para el índice dentro de cada línea. Si hay 32 KB de caché en líneas de 8 bytes, esto resulta en 4096 líneas de caché. En una caché de correspondencia directa (es decir, asociativa por conjuntos de una vía) cada conjunto tiene una única línea. Tenemos por lo tanto 4096 conjuntos posibles que pueden distinguirse con 12 bits de set. Los 17 bits restantes se asignarán al tag. 2. Las soluciones son las siguientes, siguiendo la misma lógica del punto 1: Estrategia Tag Set Index Mapeo directo 17 12 3 Asociativa por conjuntos de 2 vías 18 11 3 Asociativa por conjuntos de 8 vías 21 9 3 Completamente asociativa 29 0 3 3. Para realizar este ejercicio debe simplemente decodificarse cada dirección a los valores de tag, set e index y simularse la ejecución, teniendo siempre presente el estado de la caché. En la columna caché se registra el estado de la caché al final de cada acceso a memoria, con el formato { línea: tag }. Dirección Tag Line Index Res Caché Nota 0x70040 14 8 0 Miss {8: 14} 0x70042 14 8 2 Hit {8: 14} 0x70044 14 8 4 Hit {8: 14} 0x70046 14 8 6 Hit {8: 14} 0x70048 14 9 0 Miss {8:14, 9: 14} 0x8803c 17 7 4 Miss {8:14, 9: 14, 7: 17} 0x8803e 17 7 6 Hit {8:14, 9: 14, 7: 17} 0x88040 17 8 0 Miss {8: 17, 9: 14, 7: 17} Desalojo 0x70044 14 8 4 Miss {8: 14, 8: 14, 7: 17} Desalojo 0xb803f 23 7 7 Miss {8: 23, 8: 14, 7: 23} Acceso desalineado Un acceso desalineado es una situación especial que se da cuando se realiza un acceso a memoria en una posición cuya palabra sobrepasa el límite de la línea de caché. En este caso, para satisfacer el último pedido de lectura desde caché, será necesario cargar dos líneas de caché en vez de una. Esto tiene graves ramificaciones de performance, y si bien lo contabilizamos como un único miss, su costo es en general superior al de un miss convencional. El hit rate es HR = hits / (hits + misses) = 4 / (4 + 6) = 4/10 = 0.4 = 40 % Ejercicio 2 Se desea dotar a la máquina ORGA1 de una caché asociativa por conjuntos de 2 vías con un tamaño total de 2KB a razón de 4 palabras por línea. La política de desalojo elegida es FIFO. 1. Indique la cantidad de bits de los campos tag, line e index. 2. Contabilice los accesos a memoria. ¿Cual será el hit rate en la ejecución de este programa? MOV R2, 0 MOV R3, 0x1D55 MOV R4, 0x3755 MOV R5, 0x0757 ADD R2, [R3] ADD R2, [R4] ADD R2, [R5] ADD R2, [R5+1] ADD R2, [R3+1] ADD R2, [R4+1] 3. Si se considera que un acceso a memoria insume 50 ciclos y un acceso a caché solo 1, indique cuantos ciclos se ahorran al implementar esta memoria caché. 4. ¿Como mejoraría el programa para aprovechar mejor la caché de la computadora? Solución 1. Cada línea de caché tiene 4 palabras por línea, o sea 8 bytes. Sin embargo, como en este caso la unidad direccionable es la palabra, serán suficientes 2 bits de index. Con 2 KB de memoria caché a razón de 8 bytes por línea, tendremos 256 líneas, que agruparemos en 128 conjuntos de 2 vías. Por lo tanto, utilizaremos 7 bits de set. Finalmente, los 7 bits restantes estarán destinados al tag. 2. Para contabilizar los accesos a memoria de un programa en ensamblador, es necesario primero convertirlo a una secuencia de lecturas de memoria realizando la decodificación de instrucciones. Es importante no olvidar que el fetch de instrucciones y operandos también consta de accesos a memoria! A continuación, simularemos la ejecución. Instrucción Accesos Tag Set Index Res Caché MOV R2, 0 0x0 0 0 0 Miss 0: 0 0x1 0 0 1 Hit Idem 0x2 0 0 2 Hit Idem 0x3 0 0 3 Hit Idem 0x4 0 1 0 Miss 0: 0, 1: 0 0x5 0 1 1 Hit Idem 0x6 0 1 2 Hit Idem 0x7 0 1 3 Hit Idem 0x8 0 2 0 Miss 0: 0, 1: 0, 2: 0 0x1D55 14 85 1 Miss 0: 0, 1: 0, 2: 0, 85: 14 0x9 0 2 1 Hit Idem 0x3755 27 85 1 Miss 0: 0, 1: 0, 2: 0, 85: 14 y 27 0xA 0 2 2 Hit Idem 0x0757 3 85 3 Miss 0: 0, 1: 0, 2: 0, 85: 3 y 27 0xB 0 2 3 Hit Idem 0xC 0 3 0 Miss 0: 0, 1: 0, 2: 0, 85: 3 y 27, 3: 0 0x0758 3 86 0 Miss 0: 0, 1: 0, 2: 0, 85: 3 y 27, 3: 0, 86: 3 0xD 0 3 1 Hit Idem 0xE 0 3 2 Hit Idem 0x1D56 14 85 2 Miss 0: 0, 1: 0, 2: 0, 85: 3 y 14, 3: 0, 86: 3 0xF 0 3 3 Hit Idem 0x10 0 4 0 Miss 0: 0, 1: 0, 2: 0, 85: 3 y 14, 3: 0, 86: 3, 4: 0 0x3756 27 85 2 Miss 0: 0, 1: 0, 2: 0, 85: 3 y 27, 3: 0, 86: 3, 4: 0 MOV R3, 0x1D55 MOV R4, 0x3755 MOV R5, 0x0757 ADD R2, [R3] ADD R2, [R4] ADD R2, [R5] ADD R2, [R5+1] ADD R2, [R3+1] ADD R2, [R4+1] Nota Entra Desalojo Desalojo Desalojo El hit rate de esta ejecución es HR = hits / (hits + misses) = 12 / (12 + 11) = 12/23 = 0.521 = 52.1 % 3. En cada hit se ahorran 49 ciclos de reloj, puesto que el acceso cuesta un ciclo en lugar de 50. Dado que hubo 12 hits, se ahorraron 12 * 49 = 588 ciclos de reloj. 4. Se podría aumentar el nivel de asociatividad a 4, o bien (sin cambiar el CPU) se podrían reordenar los accesos a memoria de la suma para maximizar los hits.