Una caché de CPU es una caché de hardware utilizada por la unidad central de procesamiento (CPU) de una computadora para reducir el costo promedio (tiempo o energía) para acceder a los datos de la memoria principal . [1] Una caché es una memoria más pequeña y rápida, ubicada más cerca de un núcleo de procesador , que almacena copias de los datos de las ubicaciones de memoria principal utilizadas con frecuencia . La mayoría de las CPU tienen una jerarquía de múltiples niveles de caché (L1, L2, a menudo L3 y rara vez incluso L4), con diferentes cachés específicos de instrucciones y datos en el nivel 1. [2] La memoria caché generalmente se implementa con memoria estática de acceso aleatorio (SRAM), en las CPU modernas con mucho la parte más grande de ellas por área de chip, pero la SRAM no siempre se usa para todos los niveles (de caché I o D), o incluso para cualquier nivel, a veces algunos de los últimos o todos los niveles se implementan con eDRAM .
Existen otros tipos de cachés (que no se cuentan dentro del "tamaño de caché" de los cachés más importantes mencionados anteriormente), como el búfer de búsqueda de traducción (TLB), que es parte de la unidad de administración de memoria (MMU) que tienen la mayoría de las CPU.
Al intentar leer o escribir en una ubicación de la memoria principal, el procesador verifica si los datos de esa ubicación ya están en la memoria caché. Si es así, el procesador leerá o escribirá en la memoria caché en lugar de hacerlo en la memoria principal, que es mucho más lenta.
Muchas CPU de escritorio , servidor e industriales modernas tienen al menos tres niveles independientes de cachés (L1, L2 y L3) y diferentes tipos de cachés:
Los primeros ejemplos de cachés de CPU incluyen el Atlas 2 [3] y el IBM System/360 Model 85 [4] [5] en la década de 1960. Las primeras CPU que usaban un caché tenían solo un nivel de caché; a diferencia del caché de nivel 1 posterior, no estaba dividido en L1d (para datos) y L1i (para instrucciones). El caché L1 dividido comenzó en 1976 con la CPU IBM 801 , [6] [7] se convirtió en la corriente principal a fines de la década de 1980 y en 1997 ingresó al mercado de CPU integradas con ARMv5TE. En 2015, incluso los SoC de menos de un dólar dividieron el caché L1. También tienen cachés L2 y, para procesadores más grandes, también cachés L3. El caché L2 generalmente no está dividido y actúa como un repositorio común para el caché L1 ya dividido. Cada núcleo de un procesador multinúcleo tiene un caché L1 dedicado y, por lo general, no se comparte entre los núcleos. La caché L2 y las cachés de nivel superior pueden compartirse entre los núcleos. La caché L4 actualmente no es común y, por lo general, es una memoria de acceso aleatorio dinámica (DRAM) en un chip o matriz independiente, en lugar de una memoria de acceso aleatorio estática (SRAM). Una excepción a esto es cuando se utiliza eDRAM para todos los niveles de caché, hasta L1. Históricamente, L1 también estaba en una matriz independiente, sin embargo, tamaños de matriz más grandes han permitido la integración de esta y otros niveles de caché, con la posible excepción del último nivel. Cada nivel adicional de caché tiende a ser más grande y a estar optimizado de manera diferente.
Los cachés (como históricamente para RAM) generalmente se han dimensionado en potencias de: 2, 4, 8, 16, etc. KiB ; cuando se alcanzaron tamaños de MiB (es decir, para no L1 más grandes), muy pronto el patrón se rompió, para permitir cachés más grandes sin verse forzados al paradigma de duplicar el tamaño, por ejemplo, con Intel Core 2 Duo con 3 MiB de caché L2 en abril de 2008. Esto sucedió mucho más tarde para los cachés L1, ya que su tamaño generalmente sigue siendo una pequeña cantidad de KiB. Sin embargo, el IBM zEC12 de 2012 es una excepción, al obtener un caché de datos L1 inusualmente grande de 96 KiB para su época, y, por ejemplo, el IBM z13 que tiene un caché de instrucciones L1 de 96 KiB (y un caché de datos L1 de 128 KiB), [8] y los procesadores basados en Intel Ice Lake de 2018, que tienen un caché de datos L1 de 48 KiB y un caché de instrucciones L1 de 48 KiB. En 2020, algunas CPU Intel Atom (con hasta 24 núcleos) tienen tamaños de caché de (múltiplos de) 4,5 MiB y 15 MiB. [9] [10]
Los datos se transfieren entre la memoria y la caché en bloques de tamaño fijo, denominados líneas de caché o bloques de caché . Cuando se copia una línea de caché de la memoria a la caché, se crea una entrada de caché. La entrada de caché incluirá los datos copiados, así como la ubicación de memoria solicitada (denominada etiqueta).
Cuando el procesador necesita leer o escribir una ubicación en la memoria, primero busca una entrada correspondiente en la caché. La caché busca el contenido de la ubicación de memoria solicitada en cualquier línea de caché que pueda contener esa dirección. Si el procesador encuentra que la ubicación de memoria está en la caché, se ha producido un acierto de caché . Sin embargo, si el procesador no encuentra la ubicación de memoria en la caché, se ha producido un error de caché . En el caso de un acierto de caché, el procesador lee o escribe inmediatamente los datos en la línea de caché. En caso de un error de caché, la caché asigna una nueva entrada y copia los datos de la memoria principal; luego, la solicitud se cumple a partir del contenido de la caché.
Para hacer espacio para la nueva entrada en caso de error de caché, la caché puede tener que expulsar una de las entradas existentes. La heurística que utiliza para elegir la entrada a expulsar se denomina política de reemplazo. El problema fundamental con cualquier política de reemplazo es que debe predecir qué entrada de caché existente es menos probable que se use en el futuro. Predecir el futuro es difícil, por lo que no existe un método perfecto para elegir entre la variedad de políticas de reemplazo disponibles. Una política de reemplazo popular, la menos utilizada recientemente (LRU), reemplaza la entrada a la que se accedió menos recientemente.
Marcar algunos rangos de memoria como no almacenables en caché puede mejorar el rendimiento, ya que evita el almacenamiento en caché de regiones de memoria a las que rara vez se vuelve a acceder. Esto evita la sobrecarga de cargar algo en la memoria caché sin poder reutilizarlo. Las entradas de la memoria caché también se pueden deshabilitar o bloquear según el contexto.
Si se escriben datos en la caché, en algún momento también deben escribirse en la memoria principal; el momento de esta escritura se conoce como política de escritura. En una caché de escritura simultánea , cada escritura en la caché provoca una escritura en la memoria principal. Alternativamente, en una caché de escritura diferida o de copia diferida, las escrituras no se reflejan inmediatamente en la memoria principal y, en cambio, la caché rastrea qué ubicaciones se han sobreescrito, marcándolas como sucias . Los datos en estas ubicaciones se vuelven a escribir en la memoria principal solo cuando se expulsan de la caché. Por este motivo, una falla de lectura en una caché de escritura diferida a veces puede requerir dos accesos a la memoria para su servicio: uno para escribir primero la ubicación sucia en la memoria principal y luego otro para leer la nueva ubicación desde la memoria. Además, una escritura en una ubicación de la memoria principal que aún no está asignada en una caché de escritura diferida puede expulsar una ubicación ya sucia, liberando así ese espacio de caché para la nueva ubicación de memoria.
También existen políticas intermedias. La memoria caché puede ser de escritura simultánea, pero las escrituras pueden mantenerse en una cola de datos de almacenamiento temporalmente, generalmente para que se puedan procesar varios almacenamientos juntos (lo que puede reducir los tiempos de respuesta del bus y mejorar su utilización).
Los datos almacenados en caché de la memoria principal pueden ser modificados por otras entidades (por ejemplo, periféricos que utilizan acceso directo a memoria (DMA) u otro núcleo en un procesador multinúcleo ), en cuyo caso la copia en la caché puede quedar obsoleta o desactualizada. Alternativamente, cuando una CPU en un sistema multiprocesador actualiza datos en la caché, las copias de datos en cachés asociados con otras CPU quedan obsoletas. Los protocolos de comunicación entre los administradores de caché que mantienen la coherencia de los datos se conocen como protocolos de coherencia de caché .
La medición del rendimiento de la memoria caché ha cobrado importancia en los últimos tiempos, ya que la brecha de velocidad entre el rendimiento de la memoria y el del procesador está aumentando exponencialmente. La memoria caché se introdujo para reducir esta brecha de velocidad. Por lo tanto, conocer la capacidad de la memoria caché para salvar la brecha entre la velocidad del procesador y la memoria se vuelve importante, especialmente en sistemas de alto rendimiento. La tasa de aciertos y de errores de la memoria caché desempeñan un papel importante a la hora de determinar este rendimiento. Para mejorar el rendimiento de la memoria caché, reducir la tasa de errores se convierte en uno de los pasos necesarios, entre otros. Reducir el tiempo de acceso a la memoria caché también mejora su rendimiento y ayuda a la optimización.
El tiempo que se tarda en recuperar una línea de caché de la memoria ( latencia de lectura debido a un error de caché) es importante porque la CPU se quedará sin trabajo mientras espera la línea de caché. Cuando una CPU alcanza este estado, se denomina bloqueo. A medida que las CPU se vuelven más rápidas en comparación con la memoria principal, los bloqueos debido a errores de caché desplazan más computación potencial; las CPU modernas pueden ejecutar cientos de instrucciones en el tiempo que tarda en recuperar una sola línea de caché de la memoria principal.
Se han empleado varias técnicas para mantener la CPU ocupada durante este tiempo, incluida la ejecución fuera de orden , en la que la CPU intenta ejecutar instrucciones independientes después de la instrucción que está esperando los datos de falta de caché. Otra tecnología, utilizada por muchos procesadores, es el subprocesamiento simultáneo múltiple (SMT), que permite que un subproceso alternativo utilice el núcleo de la CPU mientras el primer subproceso espera a que los recursos de CPU necesarios estén disponibles.
La política de ubicación decide dónde en la caché irá una copia de una entrada particular de la memoria principal. Si la política de ubicación es libre de elegir cualquier entrada en la caché para almacenar la copia, la caché se llama completamente asociativa . En el otro extremo, si cada entrada en la memoria principal puede ir en un solo lugar en la caché, la caché está mapeada directamente . Muchas cachés implementan un compromiso en el que cada entrada en la memoria principal puede ir a cualquiera de los N lugares en la caché, y se describen como asociativas por conjuntos de N vías. [11] Por ejemplo, la caché de datos de nivel 1 en un AMD Athlon es asociativa por conjuntos de dos vías, lo que significa que cualquier ubicación particular en la memoria principal se puede almacenar en caché en cualquiera de las dos ubicaciones en la caché de datos de nivel 1.
La elección del valor correcto de asociatividad implica un equilibrio . Si hay diez lugares a los que la política de ubicación podría haber asignado una ubicación de memoria, entonces para verificar si esa ubicación está en la caché, se deben buscar diez entradas de caché. Verificar más lugares requiere más energía y área de chip, y potencialmente más tiempo. Por otro lado, las cachés con más asociatividad sufren menos errores (ver errores de conflicto ), de modo que la CPU pierde menos tiempo leyendo desde la memoria principal lenta. La pauta general es que duplicar la asociatividad, de mapeo directo a bidireccional, o de bidireccional a cuádruple, tiene aproximadamente el mismo efecto en aumentar la tasa de aciertos que duplicar el tamaño de la caché. Sin embargo, aumentar la asociatividad más de cuatro no mejora tanto la tasa de aciertos, [12] y generalmente se hace por otras razones (ver alias virtual). Algunas CPU pueden reducir dinámicamente la asociatividad de sus cachés en estados de bajo consumo, lo que actúa como una medida de ahorro de energía. [13]
En orden de peor pero simple a mejor pero complejo:
En esta organización de caché, cada ubicación en la memoria principal puede ir en una sola entrada en la caché. Por lo tanto, una caché de mapeo directo también puede llamarse una caché "asociativa de conjunto unidireccional". No tiene una política de ubicación como tal, ya que no hay opción de qué contenido de entrada de caché desalojar. Esto significa que si dos ubicaciones se asignan a la misma entrada, pueden eliminarse continuamente entre sí. Aunque es más simple, una caché de mapeo directo debe ser mucho más grande que una asociativa para brindar un rendimiento comparable, y es más impredecible. Sea x el número de bloque en la caché, y el número de bloque de memoria y n el número de bloques en la caché, entonces el mapeo se realiza con la ayuda de la ecuación x = y mod n .
Si cada ubicación de la memoria principal se puede almacenar en cualquiera de las dos ubicaciones de la memoria caché, una pregunta lógica es: ¿cuál de las dos? El esquema más simple y más comúnmente utilizado, que se muestra en el diagrama de la derecha, es utilizar los bits menos significativos del índice de la ubicación de la memoria como índice para la memoria caché y tener dos entradas para cada índice. Una ventaja de este esquema es que las etiquetas almacenadas en la memoria caché no tienen que incluir esa parte de la dirección de la memoria principal que está implícita en el índice de la memoria caché. Dado que las etiquetas de la memoria caché tienen menos bits, requieren menos transistores, ocupan menos espacio en la placa de circuito del procesador o en el chip del microprocesador y se pueden leer y comparar más rápido. Además, el algoritmo LRU es especialmente simple, ya que solo se necesita almacenar un bit para cada par.
Una de las ventajas de una caché de mapeo directo es que permite una especulación simple y rápida . Una vez que se ha calculado la dirección, se conoce el índice de caché que podría tener una copia de esa ubicación en la memoria. Esa entrada de caché se puede leer y el procesador puede continuar trabajando con esos datos antes de terminar de verificar que la etiqueta realmente coincide con la dirección solicitada.
La idea de que el procesador utilice los datos almacenados en caché antes de que se complete la coincidencia de etiquetas también se puede aplicar a las cachés asociativas. Se puede utilizar un subconjunto de la etiqueta, llamado pista , para seleccionar solo una de las posibles entradas de caché que se asignan a la dirección solicitada. La entrada seleccionada por la pista se puede utilizar en paralelo con la comprobación de la etiqueta completa. La técnica de la pista funciona mejor cuando se utiliza en el contexto de la traducción de direcciones, como se explica a continuación.
Se han sugerido otros esquemas, como la caché sesgada [14] , donde el índice para la vía 0 es directo, como se indicó anteriormente, pero el índice para la vía 1 se forma con una función hash . Una buena función hash tiene la propiedad de que las direcciones que entran en conflicto con la asignación directa tienden a no entrar en conflicto cuando se asignan con la función hash, y por lo tanto es menos probable que un programa sufra una cantidad inesperadamente grande de errores de conflicto debido a un patrón de acceso patológico. La desventaja es la latencia adicional al calcular la función hash. [15] Además, cuando llega el momento de cargar una nueva línea y desalojar una línea anterior, puede ser difícil determinar qué línea existente se utilizó menos recientemente, porque la nueva línea entra en conflicto con datos en diferentes índices en cada vía; el seguimiento de LRU para cachés no sesgados generalmente se realiza por conjunto. Sin embargo, los cachés asociativos sesgados tienen ventajas importantes sobre los asociativos por conjunto convencionales. [16]
Una verdadera caché asociativa de conjuntos prueba todas las formas posibles simultáneamente, utilizando algo así como una memoria direccionable por contenido . Una caché pseudoasociativa prueba cada forma posible de a una por vez. Una caché de hash-rehash y una caché asociativa de columnas son ejemplos de una caché pseudoasociativa.
En el caso común de encontrar un resultado de la primera manera probada, un caché pseudoasociativo es tan rápido como un caché mapeado directamente, pero tiene una tasa de errores de conflicto mucho menor que un caché mapeado directamente, más cercana a la tasa de errores de un caché completamente asociativo. [15]
En comparación con una caché de mapeo directo, una caché asociativa de conjunto tiene un número reducido de bits para su índice de conjunto de caché que se mapea a un conjunto de caché, donde permanecen múltiples vías o bloques, como 2 bloques para una caché asociativa de conjunto de 2 vías y 4 bloques para una caché asociativa de conjunto de 4 vías. En comparación con una caché de mapeo directo, los bits de índice de caché no utilizados se convierten en parte de los bits de etiqueta. Por ejemplo, una caché asociativa de conjunto de 2 vías contribuye con 1 bit a la etiqueta y una caché asociativa de conjunto de 4 vías contribuye con 2 bits a la etiqueta. La idea básica de la caché multicolumna [17] es usar el índice de conjunto para mapear a un conjunto de caché como lo hace una caché asociativa de conjunto convencional, y usar los bits de etiqueta agregados para indexar una vía en el conjunto. Por ejemplo, en una caché asociativa de conjunto de 4 vías, los dos bits se usan para indexar la vía 00, la vía 01, la vía 10 y la vía 11, respectivamente. Esta indexación de caché doble se denomina “mapeo de ubicación principal” y su latencia es equivalente a un acceso mapeado directo. Amplios experimentos en el diseño de cachés multicolumna [17] muestran que la tasa de aciertos en las ubicaciones principales es tan alta como el 90%. Si el mapeo de caché entra en conflicto con un bloque de caché en la ubicación principal, el bloque de caché existente se moverá a otra ruta de caché en el mismo conjunto, lo que se denomina “ubicación seleccionada”. Debido a que el bloque de caché recién indexado es un bloque usado más recientemente (MRU), se coloca en la ubicación principal en la caché multicolumna teniendo en cuenta la localidad temporal. Dado que la caché multicolumna está diseñada para una caché con una alta asociatividad, la cantidad de rutas en cada conjunto es alta; por lo tanto, es fácil encontrar una ubicación seleccionada en el conjunto. Se mantiene un índice de ubicación seleccionada por un hardware adicional para la ubicación principal en un bloque de caché. [ cita requerida ]
La caché multicolumna mantiene una alta tasa de aciertos debido a su alta asociatividad, y tiene una latencia baja comparable a la de una caché de mapeo directo debido a su alto porcentaje de aciertos en las ubicaciones principales. Los conceptos de ubicaciones principales y ubicaciones seleccionadas en la caché multicolumna se han utilizado en varios diseños de caché en el chip ARM Cortex R, [18] la memoria caché de predicción de direcciones de Intel, [19] la memoria caché asociativa multidireccional reconfigurable de IBM [20] y la selección de direcciones de reemplazo de caché dinámica de Oracle basada en bits de tabulación de dirección. [21]
Las entradas de fila de caché generalmente tienen la siguiente estructura:
El bloque de datos (línea de caché) contiene los datos reales extraídos de la memoria principal. La etiqueta contiene (parte de) la dirección de los datos reales extraídos de la memoria principal. Los bits de bandera se describen a continuación.
El "tamaño" de la caché es la cantidad de datos de la memoria principal que puede contener. Este tamaño se puede calcular como la cantidad de bytes almacenados en cada bloque de datos multiplicada por la cantidad de bloques almacenados en la caché. (Los bits de etiqueta, indicador y código de corrección de errores no se incluyen en el tamaño, [22] aunque sí afectan el área física de una caché).
Una dirección de memoria efectiva que va junto con la línea de caché (bloque de memoria) se divide ( MSB a LSB ) en la etiqueta, el índice y el desplazamiento del bloque. [23] [24]
El índice describe en qué conjunto de caché se han colocado los datos. La longitud del índice es de bits para los conjuntos de caché s .
El desplazamiento de bloque especifica los datos deseados dentro del bloque de datos almacenado dentro de la fila de caché. Normalmente, la dirección efectiva está en bytes, por lo que la longitud del desplazamiento de bloque es bits, donde b es la cantidad de bytes por bloque de datos. La etiqueta contiene los bits más significativos de la dirección, que se verifican con todas las filas del conjunto actual (el conjunto se ha recuperado por índice) para ver si este conjunto contiene la dirección solicitada. Si es así, se produce un acierto de caché. La longitud de la etiqueta en bits es la siguiente:
tag_length = address_length - index_length - block_offset_length
Algunos autores se refieren al desplazamiento del bloque simplemente como el "desplazamiento" [25] o el "desplazamiento". [26] [27]
El procesador Pentium 4 original tenía una caché de datos L1 asociativa de cuatro conjuntos de 8 KiB de tamaño, con bloques de caché de 64 bytes. Por lo tanto, hay 8 KiB / 64 = 128 bloques de caché. El número de conjuntos es igual al número de bloques de caché dividido por el número de formas de asociatividad, lo que da como resultado 128 / 4 = 32 conjuntos y, por lo tanto, 2 5 = 32 índices diferentes. Hay 2 6 = 64 posibles desplazamientos. Dado que la dirección de la CPU tiene 32 bits de ancho, esto implica 32 − 5 − 6 = 21 bits para el campo de etiqueta.
El procesador Pentium 4 original también tenía una caché L2 integrada asociativa de ocho vías de 256 KiB de tamaño, con bloques de caché de 128 bytes. Esto implica 32 − 8 − 7 = 17 bits para el campo de etiqueta. [25]
Una caché de instrucciones requiere solo un bit de indicador por entrada de fila de caché: un bit válido. El bit válido indica si un bloque de caché se ha cargado o no con datos válidos.
Al encender el sistema, el hardware configura todos los bits válidos en todas las cachés como "inválidos". Algunos sistemas también configuran un bit válido como "inválido" en otras ocasiones, como cuando el hardware de espionaje de bus multimaestro en la caché de un procesador escucha una transmisión de dirección de otro procesador y se da cuenta de que ciertos bloques de datos en la caché local ahora están obsoletos y deberían marcarse como inválidos.
Una caché de datos normalmente requiere dos bits de bandera por línea de caché: un bit válido y un bit sucio . Tener un bit sucio activado indica que la línea de caché asociada ha cambiado desde que se leyó de la memoria principal ("sucia"), lo que significa que el procesador ha escrito datos en esa línea y el nuevo valor no se ha propagado hasta la memoria principal.
Un error de caché es un intento fallido de leer o escribir un fragmento de datos en la caché, lo que da como resultado un acceso a la memoria principal con una latencia mucho mayor. Hay tres tipos de errores de caché: error de lectura de instrucciones, error de lectura de datos y error de escritura de datos.
Los errores de lectura de caché de una caché de instrucciones generalmente causan el mayor retraso, porque el procesador, o al menos el hilo de ejecución , tiene que esperar (bloquearse) hasta que la instrucción se recupera de la memoria principal. Los errores de lectura de caché de una caché de datos generalmente causan un retraso menor, porque las instrucciones que no dependen de la lectura de caché se pueden emitir y continuar la ejecución hasta que los datos se devuelvan de la memoria principal, y las instrucciones dependientes pueden reanudar la ejecución. Los errores de escritura de caché en una caché de datos generalmente causan el retraso más corto, porque la escritura se puede poner en cola y hay pocas limitaciones en la ejecución de instrucciones posteriores; el procesador puede continuar hasta que la cola esté llena. Para obtener una introducción detallada a los tipos de errores, consulte medición y métrica del rendimiento de caché .
La mayoría de las CPU de propósito general implementan algún tipo de memoria virtual . En resumen, cada programa que se ejecuta en la máquina ve su propio espacio de direcciones simplificado , que contiene código y datos solo para ese programa, o todos los programas se ejecutan en un espacio de direcciones virtuales común. Un programa se ejecuta calculando, comparando, leyendo y escribiendo en direcciones de su espacio de direcciones virtuales, en lugar de direcciones del espacio de direcciones físicas, lo que hace que los programas sean más simples y, por lo tanto, más fáciles de escribir.
La memoria virtual requiere que el procesador traduzca las direcciones virtuales generadas por el programa en direcciones físicas en la memoria principal. La parte del procesador que realiza esta traducción se conoce como unidad de gestión de memoria (MMU). La ruta rápida a través de la MMU puede realizar las traducciones almacenadas en el búfer de traducción de búsqueda (TLB), que es un caché de asignaciones de la tabla de páginas , la tabla de segmentos o ambas del sistema operativo .
A los efectos del presente debate, hay tres características importantes de la traducción de direcciones:
Un sistema de memoria virtual temprano, el IBM M44/44X , requería un acceso a una tabla de mapeo mantenida en la memoria central antes de cada acceso programado a la memoria principal. [28] [NB 1] Sin cachés, y con la memoria de la tabla de mapeo funcionando a la misma velocidad que la memoria principal, esto redujo efectivamente la velocidad de acceso a la memoria a la mitad. Dos de las primeras máquinas que usaban una tabla de páginas en la memoria principal para el mapeo, el IBM System/360 Model 67 y el GE 645 , tenían una pequeña memoria asociativa como caché para los accesos a la tabla de páginas en memoria. Ambas máquinas eran anteriores a la primera máquina con un caché para la memoria principal, el IBM System/360 Model 85 , por lo que el primer caché de hardware usado en un sistema informático no era un caché de datos o instrucciones, sino más bien un TLB.
Los cachés se pueden dividir en cuatro tipos, según si el índice o la etiqueta corresponden a direcciones físicas o virtuales:
La velocidad de esta recurrencia (la latencia de carga ) es crucial para el rendimiento de la CPU, y por eso la mayoría de los cachés de nivel 1 modernos están virtualmente indexados, lo que al menos permite que la búsqueda de TLB de la MMU proceda en paralelo con la obtención de datos de la RAM del caché.
Sin embargo, la indexación virtual no es la mejor opción para todos los niveles de caché. El costo de lidiar con alias virtuales aumenta con el tamaño de la caché y, como resultado, la mayoría de las cachés de nivel 2 y superiores están indexadas físicamente.
Históricamente, los cachés han utilizado direcciones físicas y virtuales para las etiquetas de caché, aunque el etiquetado virtual ahora es poco común. Si la búsqueda de TLB puede finalizar antes que la búsqueda de RAM de caché, entonces la dirección física está disponible a tiempo para la comparación de etiquetas y no hay necesidad de etiquetado virtual. Por lo tanto, los cachés grandes tienden a etiquetarse físicamente y solo los cachés pequeños de latencia muy baja se etiquetan virtualmente. En las CPU de propósito general recientes, el etiquetado virtual ha sido reemplazado por vhints, como se describe a continuación.
Una caché que se basa en la indexación y el etiquetado virtuales se vuelve inconsistente después de que la misma dirección virtual se asigna a diferentes direcciones físicas ( homónima ), lo que se puede resolver utilizando la dirección física para el etiquetado o almacenando el identificador del espacio de direcciones en la línea de caché. Sin embargo, el último enfoque no ayuda contra el problema de los sinónimos , en el que varias líneas de caché terminan almacenando datos para la misma dirección física. Escribir en dichas ubicaciones puede actualizar solo una ubicación en la caché, dejando las otras con datos inconsistentes. Este problema se puede resolver utilizando diseños de memoria que no se superpongan para diferentes espacios de direcciones, o de lo contrario, la caché (o una parte de ella) debe vaciarse cuando cambia la asignación. [34]
La gran ventaja de las etiquetas virtuales es que, en el caso de las cachés asociativas, permiten que la coincidencia de etiquetas se realice antes de que se realice la traducción virtual a física. Sin embargo, las pruebas de coherencia y los desalojos presentan una dirección física para la acción. El hardware debe tener algún medio para convertir las direcciones físicas en un índice de caché, generalmente almacenando etiquetas físicas y etiquetas virtuales. A modo de comparación, una caché etiquetada físicamente no necesita mantener etiquetas virtuales, lo que es más sencillo. Cuando se elimina una asignación virtual a física de la TLB, las entradas de caché con esas direcciones virtuales deberán eliminarse de alguna manera. Alternativamente, si se permiten entradas de caché en páginas no asignadas por la TLB, entonces esas entradas deberán eliminarse cuando se cambien los derechos de acceso en esas páginas en la tabla de páginas.
El sistema operativo también puede garantizar que no haya alias virtuales residentes simultáneamente en la memoria caché. El sistema operativo garantiza esto al aplicar la coloración de página, que se describe a continuación. Algunos de los primeros procesadores RISC (SPARC, RS/6000) adoptaron este enfoque. No se ha utilizado recientemente, ya que el costo de hardware para detectar y expulsar alias virtuales ha disminuido y la complejidad del software y la penalización del rendimiento de la coloración de página perfecta han aumentado.
Puede resultar útil distinguir las dos funciones de las etiquetas en una caché asociativa: se utilizan para determinar en qué dirección del conjunto de entradas se debe seleccionar y se utilizan para determinar si la caché acertó o falló. La segunda función siempre debe ser correcta, pero se permite que la primera función adivine y obtenga la respuesta incorrecta ocasionalmente.
Algunos procesadores (por ejemplo, los primeros SPARC) tienen cachés con etiquetas virtuales y físicas. Las etiquetas virtuales se utilizan para la selección de rutas y las etiquetas físicas se utilizan para determinar si se acierta o no. Este tipo de caché disfruta de la ventaja de latencia de una caché con etiquetas virtuales y de la interfaz de software simple de una caché con etiquetas físicas. Sin embargo, conlleva el costo adicional de las etiquetas duplicadas. Además, durante el procesamiento de errores, las rutas alternativas de la línea de caché indexada deben investigarse en busca de alias virtuales y se deben eliminar las coincidencias.
El área adicional (y cierta latencia) se puede mitigar manteniendo pistas virtuales con cada entrada de caché en lugar de etiquetas virtuales. Estas pistas son un subconjunto o hash de la etiqueta virtual y se utilizan para seleccionar la forma de la caché de la que obtener datos y una etiqueta física. Al igual que una caché con etiquetas virtuales, puede haber una coincidencia de pista virtual pero una falta de coincidencia de etiqueta física, en cuyo caso la entrada de caché con la pista coincidente debe ser expulsada para que los accesos a la caché después del llenado de la caché en esta dirección tengan solo una coincidencia de pista. Dado que las pistas virtuales tienen menos bits que las etiquetas virtuales que las distinguen entre sí, una caché con pistas virtuales sufre más errores de conflicto que una caché con etiquetas virtuales.
Tal vez la reducción definitiva de pistas virtuales se encuentre en el Pentium 4 (núcleos Willamette y Northwood). En estos procesadores, la pista virtual es en realidad de dos bits y la caché es asociativa de cuatro vías. En efecto, el hardware mantiene una permutación simple de dirección virtual a índice de caché, de modo que no se necesita memoria direccionable por contenido (CAM) para seleccionar la correcta de las cuatro vías obtenidas.
Las cachés indexadas físicamente de gran tamaño (normalmente cachés secundarias) presentan un problema: el sistema operativo, en lugar de la aplicación, controla qué páginas entran en colisión entre sí en la caché. Las diferencias en la asignación de páginas de una ejecución de programa a la siguiente generan diferencias en los patrones de colisión de la caché, lo que puede generar diferencias muy grandes en el rendimiento del programa. Estas diferencias pueden dificultar la obtención de una temporización consistente y repetible para una ejecución de evaluación comparativa.
Para entender el problema, considere una CPU con una caché de nivel 2 de mapeo directo indexada físicamente de 1 MiB y páginas de memoria virtual de 4 KiB. Las páginas físicas secuenciales se asignan a ubicaciones secuenciales en la caché hasta que, después de 256 páginas, el patrón se repite. Podemos etiquetar cada página física con un color de 0 a 255 para indicar dónde puede ir en la caché. Las ubicaciones dentro de las páginas físicas con diferentes colores no pueden entrar en conflicto en la caché.
Los programadores que intentan hacer el máximo uso de la memoria caché pueden organizar los patrones de acceso de sus programas de modo que solo sea necesario almacenar en caché 1 MiB de datos en un momento dado, evitando así pérdidas de capacidad. Pero también deben asegurarse de que los patrones de acceso no tengan pérdidas por conflicto. Una forma de pensar en este problema es dividir las páginas virtuales que utiliza el programa y asignarles colores virtuales de la misma manera que antes se asignaban colores físicos a las páginas físicas. Los programadores pueden entonces organizar los patrones de acceso de su código de modo que no se utilicen dos páginas con el mismo color virtual al mismo tiempo. Existe una amplia literatura sobre tales optimizaciones (por ejemplo, optimización de anidamiento de bucles ), que en gran parte proviene de la comunidad de computación de alto rendimiento (HPC) .
El problema es que, si bien todas las páginas que se utilizan en un momento dado pueden tener colores virtuales diferentes, algunas pueden tener los mismos colores físicos. De hecho, si el sistema operativo asigna páginas físicas a páginas virtuales de manera aleatoria y uniforme, es extremadamente probable que algunas páginas tengan el mismo color físico y, luego, las ubicaciones de esas páginas colisionen en la memoria caché (esta es la paradoja del cumpleaños ).
La solución es que el sistema operativo intente asignar diferentes colores físicos a páginas con diferentes colores virtuales, una técnica llamada coloración de páginas . Aunque la asignación real de colores virtuales a físicos es irrelevante para el rendimiento del sistema, las asignaciones impares son difíciles de controlar y tienen poco beneficio, por lo que la mayoría de los enfoques para la coloración de páginas simplemente intentan mantener iguales los colores físicos y virtuales de las páginas.
Si el sistema operativo puede garantizar que cada página física se asigne a un solo color virtual, entonces no hay alias virtuales y el procesador puede usar cachés indexados virtualmente sin necesidad de sondas de alias virtuales adicionales durante el manejo de errores. Alternativamente, el sistema operativo puede vaciar una página del caché cada vez que cambia de un color virtual a otro. Como se mencionó anteriormente, este enfoque se utilizó para algunos de los primeros diseños SPARC y RS/6000.
La técnica de coloración de páginas de software se ha utilizado para particionar de manera efectiva la caché de último nivel (LLC) compartida en procesadores multinúcleo. [35] Intel ha adoptado esta gestión de LLC basada en el sistema operativo en procesadores multinúcleo. [36]
Los procesadores modernos tienen múltiples cachés integrados que interactúan entre sí. El funcionamiento de una caché en particular se puede especificar completamente mediante el tamaño de la caché, el tamaño del bloque de la caché, la cantidad de bloques en un conjunto, la política de reemplazo del conjunto de caché y la política de escritura de la caché (escritura simultánea o escritura diferida). [25]
Si bien todos los bloques de caché de una determinada memoria caché tienen el mismo tamaño y la misma asociatividad, normalmente las memorias caché de "nivel inferior" (denominadas memoria caché de nivel 1) tienen una menor cantidad de bloques, un tamaño de bloque menor y menos bloques en un conjunto, pero tienen tiempos de acceso muy cortos. Las memorias caché de "nivel superior" (es decir, de nivel 2 y superiores) tienen cantidades progresivamente mayores de bloques, un tamaño de bloque mayor, más bloques en un conjunto y tiempos de acceso relativamente más largos, pero siguen siendo mucho más rápidas que la memoria principal.
La política de reemplazo de entradas de caché está determinada por un algoritmo de caché que los diseñadores del procesador seleccionan para implementar. En algunos casos, se proporcionan varios algoritmos para diferentes tipos de cargas de trabajo.
Las CPU segmentadas acceden a la memoria desde varios puntos de la segmentación : obtención de instrucciones, traducción de direcciones virtuales a físicas y obtención de datos (consulte la segmentación RISC clásica ). El diseño natural es utilizar diferentes cachés físicos para cada uno de estos puntos, de modo que no sea necesario programar ningún recurso físico para dar servicio a dos puntos de la segmentación. Por lo tanto, la segmentación termina naturalmente con al menos tres cachés independientes (instrucción, TLB y datos), cada una especializada en su función particular.
Un caché de víctima es un caché que se utiliza para almacenar bloques expulsados de un caché de CPU tras su reemplazo. El caché de víctima se encuentra entre el caché principal y su ruta de recarga, y contiene solo aquellos bloques de datos que fueron expulsados del caché principal. El caché de víctima suele ser completamente asociativo y está destinado a reducir la cantidad de errores de conflicto. Muchos programas de uso común no requieren una asignación asociativa para todos los accesos. De hecho, solo una pequeña fracción de los accesos a la memoria del programa requieren alta asociatividad. El caché de víctima explota esta propiedad al proporcionar alta asociatividad solo a estos accesos. Fue introducido por Norman Jouppi de DEC en 1990. [37]
La variante Crystalwell [38] de los procesadores Haswell de Intel introdujo un caché de nivel 4 eDRAM de 128 MiB en el paquete que sirve como caché de víctima para el caché de nivel 3 de los procesadores. [39] En la microarquitectura Skylake, el caché de nivel 4 ya no funciona como caché de víctima. [40]
Uno de los ejemplos más extremos de especialización de la memoria caché es la memoria caché de seguimiento (también conocida como memoria caché de seguimiento de ejecución ) que se encuentra en los microprocesadores Intel Pentium 4. Una memoria caché de seguimiento es un mecanismo para aumentar el ancho de banda de búsqueda de instrucciones y disminuir el consumo de energía (en el caso del Pentium 4) mediante el almacenamiento de rastros de instrucciones que ya se han buscado y decodificado. [41]
Una caché de trazas almacena instrucciones después de que se han decodificado o cuando se retiran. Generalmente, las instrucciones se agregan a las cachés de trazas en grupos que representan bloques básicos individuales o trazas de instrucciones dinámicas. La caché de trazas del Pentium 4 almacena microoperaciones resultantes de la decodificación de instrucciones x86, lo que también proporciona la funcionalidad de una caché de microoperaciones. Con esto, la próxima vez que se necesite una instrucción, no es necesario decodificarla nuevamente en microoperaciones. [42] : 63–68
Write Coalescing Cache [43] es una caché especial que forma parte de la caché L2 en la microarquitectura Bulldozer de AMD . Los datos almacenados de ambas cachés L1D en el módulo pasan por el WCC, donde se almacenan en búfer y se fusionan. La tarea del WCC es reducir la cantidad de escrituras en la caché L2.
Una caché de microoperaciones ( caché μop , caché uop o UC ) [44] es una caché especializada que almacena microoperaciones de instrucciones decodificadas, tal como se reciben directamente de los decodificadores de instrucciones o de la caché de instrucciones. Cuando es necesario decodificar una instrucción, se comprueba la caché μop en busca de su forma decodificada, que se reutiliza si está almacenada en caché; si no está disponible, la instrucción se decodifica y luego se almacena en caché.
Uno de los primeros trabajos que describen la caché μop como una interfaz alternativa para la familia de procesadores Intel P6 es el artículo de 2001 "Micro-Operation Cache: A Power Aware Frontend for Variable Instruction Length ISA" . [45] Más tarde, Intel incluyó cachés μop en sus procesadores Sandy Bridge y en microarquitecturas sucesivas como Ivy Bridge y Haswell . [42] : 121–123 [46] AMD implementó una caché μop en su microarquitectura Zen . [47]
La obtención de instrucciones predecodificadas completas elimina la necesidad de decodificar repetidamente instrucciones complejas de longitud variable en microoperaciones más simples de longitud fija, y simplifica el proceso de predicción, obtención, rotación y alineación de las instrucciones obtenidas. Una caché μop descarga de manera efectiva el hardware de obtención y decodificación, lo que reduce el consumo de energía y mejora el suministro de microoperaciones decodificadas al frontend. La caché μop también aumenta el rendimiento al entregar de manera más consistente microoperaciones decodificadas al backend y eliminar varios cuellos de botella en la lógica de obtención y decodificación de la CPU. [45] [46]
Una caché μop tiene muchas similitudes con una caché de trazas, aunque una caché μop es mucho más simple y, por lo tanto, proporciona una mejor eficiencia energética; esto la hace más adecuada para implementaciones en dispositivos alimentados por batería. La principal desventaja de la caché de trazas, que conduce a su ineficiencia energética, es la complejidad del hardware requerida para su decisión heurística sobre el almacenamiento en caché y la reutilización de trazas de instrucciones creadas dinámicamente. [48]
Un caché de destino de rama o caché de instrucciones de destino de rama , el nombre utilizado en los microprocesadores ARM , [49] es un caché especializado que contiene las primeras instrucciones en el destino de una rama tomada. Esto lo utilizan los procesadores de baja potencia que no necesitan un caché de instrucciones normal porque el sistema de memoria es capaz de entregar instrucciones lo suficientemente rápido como para satisfacer a la CPU sin uno. Sin embargo, esto solo se aplica a instrucciones consecutivas en secuencia; aún se necesitan varios ciclos de latencia para reiniciar la búsqueda de instrucciones en una nueva dirección, lo que causa algunos ciclos de burbuja de canalización después de una transferencia de control. Un caché de destino de rama proporciona instrucciones para esos pocos ciclos evitando un retraso después de la mayoría de las ramas tomadas.
Esto permite un funcionamiento a máxima velocidad con un caché mucho más pequeño que un caché de instrucciones de tiempo completo tradicional.
El caché inteligente es un método de almacenamiento en caché de nivel 2 o nivel 3 para múltiples núcleos de ejecución, desarrollado por Intel .
Smart Cache comparte la memoria caché real entre los núcleos de un procesador multinúcleo . En comparación con una caché dedicada por núcleo, la tasa general de errores de caché disminuye cuando los núcleos no requieren partes iguales del espacio de caché. En consecuencia, un solo núcleo puede usar la caché de nivel 2 o nivel 3 completa mientras los otros núcleos están inactivos. [50] Además, la caché compartida hace que sea más rápido compartir memoria entre diferentes núcleos de ejecución. [51]
Otro problema es el equilibrio fundamental entre la latencia de la caché y la tasa de aciertos. Las cachés más grandes tienen mejores tasas de aciertos pero una latencia más larga. Para abordar este equilibrio, muchas computadoras utilizan múltiples niveles de caché, con cachés pequeños y rápidos respaldados por cachés más grandes y lentos. Las cachés de múltiples niveles generalmente funcionan verificando primero la caché más rápida, nivel 1 ( L1 ); si acierta, el procesador continúa a alta velocidad. Si esa caché más pequeña falla, se verifica la siguiente caché más rápida, nivel 2 ( L2 ), y así sucesivamente, antes de acceder a la memoria externa.
A medida que la diferencia de latencia entre la memoria principal y la caché más rápida se ha vuelto más grande, algunos procesadores han comenzado a utilizar hasta tres niveles de caché en chip. Los diseños sensibles al precio utilizaron esto para llevar toda la jerarquía de caché al chip, pero en la década de 2010, algunos de los diseños de mayor rendimiento volvieron a tener grandes cachés fuera del chip, que a menudo se implementan en eDRAM y se montan en un módulo de múltiples chips , como un cuarto nivel de caché. En casos raros, como en la CPU mainframe IBM z15 (2019), todos los niveles hasta L1 se implementan mediante eDRAM, reemplazando a SRAM por completo (para la caché, SRAM todavía se usa para registros). El Apple M1 basado en ARM tiene una caché L1 de 192 KiB para cada uno de los cuatro núcleos de alto rendimiento, una cantidad inusualmente grande; sin embargo, los cuatro núcleos de alta eficiencia solo tienen 128 KiB.
Los beneficios de las cachés L3 y L4 dependen de los patrones de acceso de la aplicación. Algunos ejemplos de productos que incorporan cachés L3 y L4 son los siguientes:
Finalmente, en el otro extremo de la jerarquía de memoria, el archivo de registros de la CPU puede considerarse el caché más pequeño y rápido del sistema, con la característica especial de que está programado en software, generalmente por un compilador, ya que asigna registros para almacenar valores recuperados de la memoria principal para, por ejemplo, la optimización del anidamiento de bucles . Sin embargo, con el cambio de nombre de los registros , la mayoría de las asignaciones de registros del compilador se reasignan dinámicamente por hardware en tiempo de ejecución en un banco de registros, lo que permite a la CPU romper las dependencias de datos falsos y, por lo tanto, aliviar los riesgos de la canalización.
Los archivos de registro a veces también tienen jerarquía: el Cray-1 (circa 1976) tenía ocho registros de dirección "A" y ocho registros de datos escalares "S" que eran generalmente utilizables. También había un conjunto de 64 registros de dirección "B" y 64 registros de datos escalares "T" a los que se accedía más lentamente, pero que eran más rápidos que la memoria principal. Los registros "B" y "T" se proporcionaron porque el Cray-1 no tenía caché de datos (sin embargo, el Cray-1 sí tenía caché de instrucciones).
Al considerar un chip con múltiples núcleos , surge la pregunta de si las cachés deberían ser compartidas o locales para cada núcleo. Implementar una caché compartida inevitablemente introduce más cableado y complejidad. Pero, por otro lado, tener una caché por chip , en lugar de núcleo , reduce en gran medida la cantidad de espacio necesario y, por lo tanto, se puede incluir una caché más grande.
Por lo general, compartir la caché L1 no es deseable porque el aumento resultante en la latencia haría que cada núcleo funcione considerablemente más lento que un chip de un solo núcleo. Sin embargo, para la caché de nivel más alto, la última llamada antes de acceder a la memoria, tener una caché global es deseable por varias razones, como permitir que un solo núcleo use toda la caché, reducir la redundancia de datos al hacer posible que diferentes procesos o subprocesos compartan datos en caché y reducir la complejidad de los protocolos de coherencia de caché utilizados. [53] Por ejemplo, un chip de ocho núcleos con tres niveles puede incluir una caché L1 para cada núcleo, una caché L2 intermedia para cada par de núcleos y una caché L3 compartida entre todos los núcleos.
Una caché de nivel más alto compartida, a la que se llama antes de acceder a la memoria, se suele denominar caché de último nivel (LLC). Se utilizan técnicas adicionales para aumentar el nivel de paralelismo cuando la LLC se comparte entre varios núcleos, incluida la división en varias partes que abordan ciertos rangos de direcciones de memoria y a las que se puede acceder de forma independiente. [54]
En una estructura de caché separada, las instrucciones y los datos se almacenan en caché por separado, lo que significa que se utiliza una línea de caché para almacenar en caché instrucciones o datos, pero no ambos; se han demostrado varios beneficios con buffers de búsqueda de traducción de instrucciones y datos separados . [55] En una estructura unificada, esta restricción no está presente y las líneas de caché se pueden utilizar para almacenar en caché tanto instrucciones como datos.
Las cachés multinivel introducen nuevas decisiones de diseño. Por ejemplo, en algunos procesadores, todos los datos en la caché L1 también deben estar en algún lugar de la caché L2. Estas cachés se denominan estrictamente inclusivas . Otros procesadores (como el AMD Athlon ) tienen cachés exclusivas : se garantiza que los datos estén en como máximo una de las cachés L1 y L2, nunca en ambas. Sin embargo, otros procesadores (como el Intel Pentium II , III y 4 ) no requieren que los datos en la caché L1 también residan en la caché L2, aunque a menudo puede suceder. No hay un nombre universalmente aceptado para esta política intermedia; [56] [57] dos nombres comunes son "no exclusiva" y "parcialmente inclusiva".
La ventaja de las cachés exclusivas es que almacenan más datos. Esta ventaja es mayor cuando la caché L1 exclusiva es comparable a la caché L2, y disminuye si la caché L2 es muchas veces más grande que la caché L1. Cuando la L1 falla y la L2 acierta en un acceso, la línea de caché que acierta en la L2 se intercambia con una línea en la L1. Este intercambio es bastante más trabajo que simplemente copiar una línea de L2 a L1, que es lo que hace una caché inclusiva. [57]
Una ventaja de las cachés estrictamente inclusivas es que cuando los dispositivos externos u otros procesadores en un sistema multiprocesador desean eliminar una línea de caché del procesador, sólo necesitan que el procesador verifique la caché L2. En las jerarquías de caché que no imponen la inclusión, también se debe verificar la caché L1. Como desventaja, existe una correlación entre las asociatividades de las cachés L1 y L2: si la caché L2 no tiene al menos tantas formas como todas las cachés L1 juntas, la asociatividad efectiva de las cachés L1 se ve restringida. Otra desventaja de la caché inclusiva es que siempre que hay una expulsión en la caché L2, las líneas (posiblemente) correspondientes en L1 también tienen que ser expulsadas para mantener la inclusividad. Esto es bastante trabajo y daría como resultado una mayor tasa de errores L1. [57]
Otra ventaja de los cachés inclusivos es que el caché más grande puede utilizar líneas de caché más grandes, lo que reduce el tamaño de las etiquetas de caché secundarias. (Los cachés exclusivos requieren que ambos cachés tengan líneas de caché del mismo tamaño, de modo que las líneas de caché se puedan intercambiar en caso de un error de L1 y un acierto de L2). Si el caché secundario es un orden de magnitud más grande que el primario, y los datos del caché son un orden de magnitud más grande que las etiquetas de caché, esta área de etiqueta guardada puede ser comparable al área incremental necesaria para almacenar los datos del caché L1 en el L2. [58]
La memoria scratchpad (SPM), también conocida como scratchpad, scratchpad RAM o tienda local en terminología informática, es una memoria interna de alta velocidad utilizada para el almacenamiento temporal de cálculos, datos y otros trabajos en progreso.
Para ilustrar tanto la especialización como el almacenamiento en caché de múltiples niveles, aquí se muestra la jerarquía de caché del núcleo K8 en la CPU AMD Athlon 64. [59]
El K8 tiene cuatro cachés especializados: un caché de instrucciones, un TLB de instrucciones , un TLB de datos y un caché de datos. Cada uno de estos cachés está especializado:
El K8 también tiene cachés de varios niveles. Hay TLB de instrucciones y datos de segundo nivel, que almacenan solo PTE que mapean 4 KiB. Tanto los cachés de instrucciones como los de datos, y los diversos TLB, pueden llenarse desde el gran caché L2 unificado . Este caché es exclusivo tanto para los cachés de instrucciones L1 como de datos, lo que significa que cualquier línea de 8 bytes solo puede estar en uno de los cachés de instrucciones L1, de datos L1 o L2. Sin embargo, es posible que una línea en el caché de datos tenga un PTE que también esté en uno de los TLB; el sistema operativo es responsable de mantener la coherencia de los TLB vaciando partes de ellos cuando se actualizan las tablas de páginas en la memoria.
El K8 también almacena en caché información que nunca se almacena en la memoria: información de predicción. Estas cachés no se muestran en el diagrama anterior. Como es habitual en esta clase de CPU, el K8 tiene una predicción de saltos bastante compleja , con tablas que ayudan a predecir si se toman saltos y otras tablas que predicen los objetivos de los saltos y las ramificaciones. Parte de esta información está asociada a instrucciones, tanto en la caché de instrucciones de nivel 1 como en la caché secundaria unificada.
El K8 utiliza un truco interesante para almacenar información de predicción con instrucciones en la caché secundaria. Las líneas en la caché secundaria están protegidas de la corrupción accidental de datos (por ejemplo, por un impacto de partículas alfa ) ya sea por ECC o por paridad , dependiendo de si esas líneas fueron expulsadas de las cachés primarias de datos o de instrucciones. Dado que el código de paridad ocupa menos bits que el código ECC, las líneas de la caché de instrucciones tienen algunos bits de repuesto. Estos bits se utilizan para almacenar en caché la información de predicción de bifurcaciones asociada con esas instrucciones. El resultado neto es que el predictor de bifurcaciones tiene una tabla de historial efectivo más grande y, por lo tanto, tiene una mejor precisión.
Otros procesadores tienen otros tipos de predictores (por ejemplo, el predictor de derivación de almacenamiento a carga en el DEC Alpha 21264 ), y es probable que varios predictores especializados florezcan en procesadores futuros.
Estos predictores son cachés en el sentido de que almacenan información cuyo cálculo es costoso. Algunos de los términos que se utilizan cuando se habla de predictores son los mismos que los que se utilizan para los cachés (se habla de un acierto en un predictor de ramificación), pero los predictores no suelen considerarse parte de la jerarquía de cachés.
El K8 mantiene la coherencia de las cachés de instrucciones y datos en el hardware, lo que significa que un almacenamiento en una instrucción que sigue de cerca a la instrucción de almacenamiento cambiará la instrucción siguiente. Otros procesadores, como los de la familia Alpha y MIPS, han dependido del software para mantener la coherencia de la caché de instrucciones. No se garantiza que los almacenamientos aparezcan en el flujo de instrucciones hasta que un programa llame a una función del sistema operativo para garantizar la coherencia.
En ingeniería informática, se utiliza una etiqueta RAM para especificar cuál de las posibles ubicaciones de memoria está almacenada actualmente en una caché de CPU. [60] [61] Para un diseño simple y de mapeo directo, se puede utilizar una SRAM rápida. Las cachés asociativas superiores suelen emplear memoria direccionable por contenido .
Las lecturas de caché son la operación de CPU más común que requiere más de un ciclo. El tiempo de ejecución del programa tiende a ser muy sensible a la latencia de un acceso a la caché de datos de nivel 1. Se invierte mucho esfuerzo de diseño y, a menudo, energía y área de silicio para hacer que las cachés sean lo más rápidas posible.
La caché más simple es una caché de mapeo directo indexada virtualmente. La dirección virtual se calcula con un sumador, la parte relevante de la dirección se extrae y se usa para indexar una SRAM, que devuelve los datos cargados. Los datos se alinean en bytes en un desplazador de bytes y desde allí se pasan a la siguiente operación. No es necesario realizar ninguna comprobación de etiquetas en el bucle interno; de hecho, ni siquiera es necesario leer las etiquetas. Más adelante en el proceso, pero antes de que se retire la instrucción de carga, se debe leer la etiqueta de los datos cargados y compararla con la dirección virtual para asegurarse de que se haya producido un error en la caché. En caso de error, la caché se actualiza con la línea de caché solicitada y se reinicia el proceso.
Una caché asociativa es más complicada, porque se debe leer algún tipo de etiqueta para determinar qué entrada de la caché seleccionar. Una caché de nivel 1 asociativa de N vías suele leer todas las N etiquetas posibles y los N datos en paralelo y, a continuación, elige los datos asociados con la etiqueta correspondiente. Las cachés de nivel 2 a veces ahorran energía leyendo primero las etiquetas, de modo que solo se lee un elemento de datos de la SRAM de datos.
El diagrama adjunto tiene como objetivo aclarar la manera en que se utilizan los distintos campos de la dirección. El bit 31 de la dirección es el más significativo, el bit 0 es el menos significativo. El diagrama muestra las SRAM, la indexación y la multiplexación para una caché de 4 KiB, asociativa por conjuntos de 2 vías, indexada y etiquetada virtualmente con líneas de 64 bytes (B), un ancho de lectura de 32 bits y una dirección virtual de 32 bits.
Como la memoria caché tiene 4 KiB y 64 líneas B, solo hay 64 líneas en la memoria caché y leemos dos a la vez desde una SRAM de etiquetas que tiene 32 filas, cada una con un par de etiquetas de 21 bits. Aunque se podría utilizar cualquier función de los bits de dirección virtual 31 a 6 para indexar las SRAM de etiquetas y datos, lo más sencillo es utilizar los bits menos significativos.
De manera similar, debido a que la memoria caché tiene 4 KiB y una ruta de lectura de 4 B, y lee de dos maneras para cada acceso, la SRAM de datos tiene 512 filas por 8 bytes de ancho.
Una caché más moderna podría tener 16 KiB, asociativa de conjuntos de 4 vías, indexada virtualmente, insinuada virtualmente y etiquetada físicamente, con 32 líneas B, ancho de lectura de 32 bits y direcciones físicas de 36 bits. La recurrencia de la ruta de lectura para una caché de este tipo es muy similar a la ruta anterior. En lugar de etiquetas, se leen vhints y se comparan con un subconjunto de la dirección virtual. Más adelante en el proceso, la dirección virtual se traduce en una dirección física por la TLB y se lee la etiqueta física (solo una, ya que vhint proporciona en qué dirección de la caché se debe leer). Finalmente, la dirección física se compara con la etiqueta física para determinar si se ha producido un acierto.
Algunos diseños SPARC han mejorado la velocidad de sus cachés L1 con algunos retrasos de compuerta al incorporar el sumador de direcciones virtuales en los decodificadores SRAM. Véase decodificador con dirección de suma .
La historia temprana de la tecnología de caché está estrechamente ligada a la invención y el uso de la memoria virtual. [ cita requerida ] Debido a la escasez y el costo de las memorias de semiconductores, las primeras computadoras mainframe de la década de 1960 usaban una jerarquía compleja de memoria física, mapeada sobre un espacio de memoria virtual plano usado por los programas. Las tecnologías de memoria abarcarían semiconductores, núcleo magnético, tambor y disco. La memoria virtual vista y usada por los programas sería plana y el almacenamiento en caché se usaría para buscar datos e instrucciones en la memoria más rápida antes del acceso del procesador. Se realizaron estudios exhaustivos para optimizar los tamaños de caché. Se descubrió que los valores óptimos dependían en gran medida del lenguaje de programación usado: Algol necesitaba el tamaño de caché más pequeño y Fortran y Cobol necesitaban los tamaños de caché más grandes. [ disputado – discutir ]
En los primeros días de la tecnología de microcomputadoras, el acceso a la memoria era apenas un poco más lento que el acceso a los registros . Pero desde la década de 1980 [62] la brecha de rendimiento entre el procesador y la memoria ha ido creciendo. Los microprocesadores han avanzado mucho más rápido que la memoria, especialmente en términos de su frecuencia de funcionamiento , por lo que la memoria se convirtió en un cuello de botella en el rendimiento. Si bien era técnicamente posible tener toda la memoria principal tan rápida como la CPU, se ha tomado un camino económicamente más viable: utilizar mucha memoria de baja velocidad, pero también introducir una pequeña memoria caché de alta velocidad para aliviar la brecha de rendimiento. Esto proporcionó un orden de magnitud más de capacidad, por el mismo precio, con solo un rendimiento combinado ligeramente reducido.
Los primeros usos documentados de una TLB fueron en el GE 645 [63] y el IBM 360/67 [64], ambos utilizaban una memoria asociativa como TLB.
El primer uso documentado de un caché de instrucciones fue en el CDC 6600. [ 65]
El primer uso documentado de un caché de datos fue en el IBM System/360 Modelo 85. [66]
El 68010 , lanzado en 1982, tiene un "modo de bucle" que puede considerarse una caché de instrucciones diminuta y especial que acelera los bucles que constan de solo dos instrucciones. El 68020 , lanzado en 1984, lo reemplazó con una caché de instrucciones típica de 256 bytes, siendo el primer procesador de la serie 68k en contar con una verdadera memoria caché en chip.
El 68030 , lanzado en 1987, es básicamente un núcleo 68020 con un caché de datos adicional de 256 bytes, una unidad de administración de memoria (MMU) en el chip, una reducción de proceso y un modo de ráfaga agregado para los cachés.
El 68040 , lanzado en 1990, tiene cachés de instrucciones y datos divididos de cuatro kilobytes cada uno.
El 68060 , lanzado en 1994, tiene lo siguiente: caché de datos de 8 KiB (asociativo de cuatro vías), caché de instrucciones de 8 KiB (asociativo de cuatro vías), búfer de instrucciones FIFO de 96 bytes, caché de bifurcación de 256 entradas y búfer MMU de caché de traducción de direcciones de 64 entradas (asociativo de cuatro vías).
A medida que los microprocesadores x86 alcanzaron velocidades de reloj de 20 MHz y superiores en el 386 , comenzaron a incluirse pequeñas cantidades de memoria caché rápida en los sistemas para mejorar el rendimiento. Esto se debió a que la DRAM utilizada para la memoria principal tenía una latencia significativa, de hasta 120 ns, así como ciclos de actualización. La caché se construyó a partir de celdas de memoria SRAM más caras, pero significativamente más rápidas , que en ese momento tenían latencias de alrededor de 10-25 ns. Las primeras cachés eran externas al procesador y generalmente se ubicaban en la placa base en forma de ocho o nueve dispositivos DIP colocados en zócalos para habilitar la caché como una característica adicional o de actualización opcional.
Algunas versiones del procesador Intel 386 podrían admitir de 16 a 256 KiB de caché externa.
Con el procesador 486 , se integró una caché de 8 KiB directamente en la matriz de la CPU. Esta caché se denominó caché de nivel 1 o L1 para diferenciarla de la caché en la placa base, más lenta, o caché de nivel 2 (L2). Estas cachés en la placa base eran mucho más grandes, siendo el tamaño más común de 256 KiB. Había algunas placas del sistema que contenían zócalos para la tarjeta hija Intel 485Turbocache que tenía 64 o 128 Kbytes de memoria caché. [67] [68] La popularidad de la caché en la placa base continuó durante la era Pentium MMX , pero se volvió obsoleta con la introducción de SDRAM y la creciente disparidad entre las velocidades de reloj del bus y las velocidades de reloj de la CPU, lo que provocó que la caché en la placa base fuera solo ligeramente más rápida que la memoria principal.
El siguiente desarrollo en la implementación de caché en los microprocesadores x86 comenzó con el Pentium Pro , que incorporó el caché secundario al mismo paquete que el microprocesador, con la misma frecuencia que el microprocesador.
Las memorias caché en placa base gozaron de una popularidad prolongada gracias a los procesadores AMD K6-2 y AMD K6-III que todavía utilizaban Socket 7 , que Intel había utilizado anteriormente con memorias caché en placa base. K6-III incluía una memoria caché L2 en chip de 256 KiB y aprovechaba la memoria caché en placa como una memoria caché de tercer nivel, denominada L3 (se produjeron placas base con hasta 2 MiB de memoria caché en placa). Después de que el Socket 7 se volviera obsoleto, la memoria caché en placa desapareció de los sistemas x86.
Las cachés de tres niveles se volvieron a utilizar por primera vez con la introducción de múltiples núcleos de procesador, donde la caché L3 se agregó a la matriz de la CPU. Se volvió común que los tamaños totales de caché fueran cada vez más grandes en las generaciones de procesadores más nuevas y, recientemente (a partir de 2011), no es raro encontrar tamaños de caché de nivel 3 de decenas de megabytes. [69]
Intel introdujo una caché de nivel 4 en el paquete con la microarquitectura Haswell . Crystalwell [38] Las CPU Haswell, equipadas con la variante GT3e de los gráficos integrados Iris Pro de Intel, cuentan efectivamente con 128 MiB de DRAM integrada ( eDRAM ) en el mismo paquete. Esta caché L4 se comparte dinámicamente entre la GPU y la CPU en el chip, y sirve como caché víctima para la caché L3 de la CPU. [39]
La CPU Apple M1 tiene una caché L1 de instrucciones de 128 o 192 KiB para cada núcleo (importante para la latencia/rendimiento de un solo subproceso), según el tipo de núcleo. Se trata de una caché L1 inusualmente grande para cualquier tipo de CPU (no solo para una computadora portátil); el tamaño total de la memoria caché no es inusualmente grande (el total es más importante para el rendimiento) para una computadora portátil, y hay tamaños totales mucho más grandes (por ejemplo, L3 o L4) disponibles en los mainframes de IBM.
Los primeros diseños de caché se centraban exclusivamente en el coste directo de la memoria caché y la RAM , así como en la velocidad de ejecución media. Los diseños de caché más recientes también tienen en cuenta la eficiencia energética , la tolerancia a fallos y otros objetivos. [70] [71]
Hay varias herramientas disponibles para los arquitectos de computadoras que pueden ayudar a explorar las compensaciones entre el tiempo del ciclo de caché, la energía y el área; el simulador de caché CACTI [72] y el simulador de conjunto de instrucciones SimpleScalar son dos opciones de código abierto.
Una caché multipuerto es una caché que puede atender más de una solicitud a la vez. Cuando accedemos a una caché tradicional, normalmente utilizamos una única dirección de memoria, mientras que en una caché multipuerto podemos solicitar N direcciones a la vez, donde N es el número de puertos que se conectan a través del procesador y la caché. El beneficio de esto es que un procesador segmentado puede acceder a la memoria desde diferentes fases de su segmentación. Otro beneficio es que permite el concepto de procesadores superescalares a través de diferentes niveles de caché.
En Cambridge se desarrollaron dos memorias de diodos túnel; una, que funcionaba muy bien, aceleraba la obtención de operandos, la otra tenía como objetivo acelerar la obtención de instrucciones. La idea era que la mayoría de las instrucciones se obedecieran en secuencia, de modo que cuando se obtenía una instrucción, esa palabra se colocaba en la memoria esclava en la ubicación dada por la dirección de obtención módulo 32; los bits restantes de la dirección de obtención también se almacenaban. Si la palabra deseada estaba en la memoria esclava, se leía desde allí en lugar de desde la memoria principal. Esto daría una importante aceleración a los bucles de instrucciones de hasta 32 instrucciones de longitud y reduciría el efecto para los bucles de hasta 64 palabras.
Caché L1 de 32 KB/núcleo, caché L2 de 4,5 MB por clúster de 4 núcleos y caché LLC compartida de hasta 15 MB.
Se ha demostrado que los cachés asociativos sesgados tienen dos ventajas principales sobre los cachés asociativos de conjuntos convencionales.