Saltar al contenido

¿Por qué el tamaño de la caché L1 es más pequeño que el de la caché L2 en la mayoría de los procesadores?

Solución:

L1 está estrechamente acoplado al núcleo de la CPU y se accede a él en cada acceso a la memoria (muy frecuente). Por lo tanto, necesita devolver los datos muy rápido (generalmente dentro del ciclo del reloj). La latencia y el rendimiento (ancho de banda) son fundamentales para el rendimiento de la caché de datos L1. (por ejemplo, latencia de cuatro ciclos, y admite dos lecturas y una escritura por parte del núcleo de la CPU en cada ciclo de reloj). Necesita muchos puertos de lectura / escritura para admitir este ancho de banda de alto acceso. Es imposible construir una caché grande con estas propiedades. Por lo tanto, los diseñadores lo mantienen pequeño, por ejemplo, 32 KB en la mayoría de los procesadores de hoy.

Se accede a L2 solo en fallas de L1, por lo que los accesos son menos frecuentes (generalmente 1/20 de L1). Por lo tanto, L2 puede tener una latencia más alta (por ejemplo, de 10 a 20 ciclos) y tener menos puertos. Esto permite a los diseñadores hacerlo más grande.


L1 y L2 juegan roles muy diferentes. Si L1 se hace más grande, aumentará la latencia de acceso de L1, lo que reducirá drásticamente el rendimiento porque hará que todas las cargas dependientes sean más lentas y más difíciles de ocultar para la ejecución fuera de orden. El tamaño L1 es apenas discutible.

Si eliminamos L2, las fallas de L1 tendremos que pasar al siguiente nivel, digamos memoria. Esto significa que una gran parte del acceso irá a la memoria, lo que implicaría que necesitamos más ancho de banda de memoria, lo que ya es un cuello de botella. Por lo tanto, mantener la L2 alrededor es favorable.

Los expertos a menudo se refieren a L1 como un filtro de latencia (ya que hace que el caso común de los aciertos de L1 sea más rápido) y a L2 como un filtro de ancho de banda, ya que reduce el uso de ancho de banda de la memoria.

Nota: He asumido una jerarquía de caché de 2 niveles en mi argumento para hacerlo más simple. En muchos de los chips multinúcleo actuales, hay una caché L3 compartida entre todos los núcleos, mientras que cada núcleo tiene su propia L1 privada y tal vez L2. En estos chips, la caché compartida de último nivel (L3) desempeña el papel de filtro de ancho de banda de memoria. L2 desempeña el papel de filtro de ancho de banda en el chip, es decir, reduce el acceso a la interconexión en el chip y el L3. Esto permite a los diseñadores utilizar una interconexión de menor ancho de banda como un anillo y un L3 de puerto único lento, lo que les permite hacer que L3 sea más grande.

Quizás vale la pena mencionar que la cantidad de puertos es un punto de diseño muy importante porque afecta la cantidad de área de chip que consume la caché. Los puertos agregan cables a la caché, lo que consume una gran cantidad de área de chip y energía.

Hay distintas razones para ello.

L2 existe en el sistema para acelerar el caso en el que hay una falta de caché L1. Si el tamaño de L1 fuera igual o mayor que el tamaño de L2, entonces L2 no podría acomodar más líneas de caché que L1, y no podría lidiar con fallas de caché de L1. Desde la perspectiva del diseño / costo, la caché L1 está vinculada al procesador y es más rápida que L2. La idea general de los cachés es que acelera el acceso al hardware más lento agregando hardware intermedio que tiene más rendimiento (y es más caro) que el hardware más lento y, sin embargo, más barato que el hardware más rápido que tiene. Incluso si decidiera duplicar la caché L1, también incrementaría L2, para acelerar las fallas de caché L1.

Entonces, ¿por qué hay caché L2? Bueno, la caché L1 suele ser más eficiente y costosa de construir, y está vinculada a un solo núcleo. Esto significa que aumentar el tamaño de L1 en una cantidad fija tendrá ese costo multiplicado por 4 en un procesador de doble núcleo, o por 8 en un procesador de cuatro núcleos. L2 generalmente es compartido por diferentes núcleos, dependiendo de la arquitectura, se puede compartir entre un par o todos los núcleos del procesador, por lo que el costo de aumentar L2 sería menor incluso si el precio de L1 y L2 fuera el mismo, que No lo es.

La respuesta de @ Aater explica algunos de los conceptos básicos. Agregaré algunos detalles más + ejemplos de la organización de caché real en Intel Haswell y AMD Piledriver, con latencias y otras propiedades, no solo el tamaño.

Para obtener algunos detalles sobre IvyBridge, consulte mi respuesta sobre “¿Cómo puede el caché ser tan rápido?”, Con un poco de discusión sobre la latencia de uso de carga general, incluido el tiempo de cálculo de direcciones y el ancho de los buses de datos entre diferentes niveles de caché.


L1 debe ser muy rápido (latencia y rendimiento), incluso si eso significa una tasa de aciertos limitada. L1d también necesita admitir almacenes de un solo byte en casi todas las arquitecturas y (en algunos diseños) accesos no alineados. Esto dificulta el uso de ECC (códigos de corrección de errores) para proteger los datos y, de hecho, algunos diseños L1d (Intel) solo usan la paridad, con mejor ECC solo en los niveles externos de caché (L2 / L3) donde se puede realizar el ECC en trozos más grandes para una sobrecarga más baja.

Es imposible diseñar un único nivel de caché que pueda proporcionar la latencia de solicitud promedio baja (promediada sobre todos los aciertos y errores) de un caché moderno de varios niveles.. Dado que los sistemas modernos tienen múltiples núcleos muy hambrientos, todos compartiendo una conexión a la misma DRAM de latencia relativamente alta, esto es esencial.

Cada núcleo necesita su propio L1 privado para la velocidad, pero al menos el último nivel de caché generalmente se comparte, por lo que un programa de múltiples subprocesos que lee los mismos datos de varios subprocesos no tiene que ir a DRAM para cada núcleo. (Y para actuar como un respaldo para los datos escritos por un núcleo y leídos por otro). Esto requiere al menos dos niveles de caché para un sistema multinúcleo sano, y es parte de la motivación de más de 2 niveles en los diseños actuales. Las modernas CPU x86 de múltiples núcleos tienen una caché rápida de 2 niveles en cada núcleo y una caché más grande y lenta compartida por todos los núcleos.

La tasa de aciertos de L1 sigue siendo muy importante, por lo que las cachés de L1 no son tan pequeñas / simples / rápidas como podrían ser, porque eso reduciría las tasas de aciertos. Por lo tanto, lograr el mismo rendimiento general requeriría niveles más altos de caché para ser más rápidos. Si los niveles más altos manejan más tráfico, su latencia es un componente más grande de la latencia promedio, y se atascan en su rendimiento con más frecuencia (o necesitan un rendimiento más alto).

Un alto rendimiento a menudo significa poder manejar múltiples lecturas y escrituras en cada ciclo, es decir, múltiples puertos. Esto ocupa más espacio y poder para la misma capacidad que una caché de menor rendimiento, por lo que esa es otra razón para que L1 se mantenga pequeña.


L1 también usa trucos de velocidad que no funcionarían si fuera más grande. es decir, la mayoría de los diseños utilizan L1 indexado virtualmente, etiquetado físicamente (VIPT), pero con todos los bits de índice provenientes de debajo del desplazamiento de la página, por lo que se comportan como PIPT (porque los bits bajos de una dirección virtual son los mismos que en la dirección física) . Esto evita sinónimos / homónimos (false coincidencias o los mismos datos están en el caché dos veces, y vea la respuesta detallada de Paul Clayton en la pregunta vinculada), pero aún permite que parte de la verificación de coincidencias / fallas ocurra en paralelo con la búsqueda de TLB. Un caché VIVT no tiene que esperar por el TLB, pero debe invalidarse en cada cambio en las tablas de páginas.

En x86 (que usa páginas de memoria virtual 4kiB), las cachés L1 asociativas de 8 vías 32kiB son comunes en los diseños modernos. Las 8 etiquetas se pueden recuperar en función de los 12 bits bajos de la dirección virtual, porque esos bits son los mismos en las direcciones virtuales y físicas (están por debajo del desplazamiento de página para páginas de 4kiB). Este truco de velocidad para cachés L1 solo funciona si son lo suficientemente pequeños y asociativos como para que el índice no dependa del resultado de TLB. 32kiB / 64B líneas / asociatividad de 8 vías = 64 (2 ^ 6) conjuntos. Entonces, los 6 bits más bajos de una dirección seleccionan bytes dentro de una línea, y los siguientes 6 bits índice un conjunto de 8 etiquetas. Este conjunto de 8 etiquetas se obtiene en paralelo con la búsqueda de TLB, por lo que las etiquetas se pueden verificar en paralelo con los bits de selección de página física del resultado de TLB para determinar cuál (si alguna) de las 8 formas de la caché contiene los datos. . (Asociatividad mínima para que una caché PIPT L1 también sea VIPT, accediendo a un conjunto sin traducir el índice a físico)

Hacer una caché L1 más grande significaría que tendría que esperar el resultado de TLB antes de que pudiera comenzar a buscar etiquetas y cargarlas en los comparadores paralelos, o tendría que aumentar la asociatividad para mantener log2 (sets) + log2 (line_size) <= 12. (More associativity means more ways per set => menos conjuntos totales = menos bits de índice). Entonces, por ejemplo, una caché de 64kiB necesitaría ser asociativa de 16 vías: todavía 64 conjuntos, pero cada conjunto tiene el doble de formas. Esto hace que aumentar el tamaño de L1 más allá del tamaño actual sea prohibitivamente caro en términos de potencia y probablemente incluso de latencia.

Gastar más de su presupuesto de energía en la lógica de caché L1D dejaría menos energía disponible para ejecución desordenada, decodificación y, por supuesto, caché L2, etc.. Hacer que todo el núcleo funcione a 4GHz y mantenga ~ 4 instrucciones por reloj (en código de alto ILP) sin fundirse requiere un diseño equilibrado. Vea este artículo: Microprocesadores modernos: ¡Una guía de 90 minutos !.

Cuanto más grande sea un caché, más perderá al vaciarlo, por lo que un caché VIVT L1 grande sería peor que el VIPT actual que funciona como PIPT. Y un L1D más grande pero de mayor latencia probablemente también sería peor.

Según @PaulClayton, las cachés L1 a menudo obtienen todos los datos en un conjunto en paralelo con las etiquetas, por lo que está listo para ser seleccionado una vez que se detecta la etiqueta correcta. El costo de energía de hacer esto escala con la asociatividad, por lo que un L1 grande altamente asociativo sería realmente malo para el uso de energía, así como para el área de la matriz (y la latencia). (En comparación con L2 y L3, no sería una gran área, pero la proximidad física es importante para la latencia. Los retrasos en la propagación de la velocidad de la luz son importantes cuando los ciclos de reloj son 1/4 de nanosegundo).

Los cachés más lentos (como L3) pueden funcionar a un voltaje / velocidad de reloj más bajo para generar menos calor. Incluso pueden usar diferentes arreglos de transistores para cada celda de almacenamiento, para hacer que la memoria esté más optimizada para potencia que para alta velocidad.

Hay muchas razones relacionadas con el uso de energía para los cachés multinivel. La energía / calor es una de las limitaciones más importantes en el diseño de CPU moderno, porque enfriar un chip diminuto es difícil. Todo es una compensación entre velocidad y potencia (y / o área del dado). Además, muchas CPU funcionan con baterías o se encuentran en centros de datos que necesitan refrigeración adicional.


L1 casi siempre se divide en cachés de datos e instrucciones separados. En lugar de un puerto de lectura adicional en un L1 unificado para admitir la recuperación de código, podemos tener un caché L1I separado vinculado a un I-TLB separado. (Las CPU modernas a menudo tienen un L2-TLB, que es un segundo nivel de caché para las traducciones que comparten L1 I-TLB y D-TLB, NO un TLB utilizado por el caché L2 normal). Esto nos da un total de 64kB de caché L1, particionado estáticamente en cachés de código y datos, por mucho más barato (y probablemente menor latencia) que un caché unificado de 64k L1 monstruoso con el mismo rendimiento total. Ya que hay por lo general, muy poca superposición entre el código y los datos, esto es una gran victoria.

L1I se puede colocar físicamente cerca de la lógica de búsqueda de código, mientras que L1D puede estar físicamente cerca de las unidades de carga / almacenamiento. Los retrasos en la línea de transmisión de la velocidad de la luz son un gran problema cuando un ciclo de reloj dura solo 1/3 de nanosegundo. El enrutamiento del cableado también es un gran problema: por ejemplo, Intel Broadwell tiene 13 capas de cobre por encima del silicio.

Split L1 ayuda mucho con la velocidad, pero L2 unificado es la mejor opción.
Algunas cargas de trabajo tienen un código muy pequeño pero tocan muchos datos. Tiene sentido que las memorias caché de nivel superior se unifiquen para adaptarse a diferentes cargas de trabajo, en lugar de realizar particiones estáticas en código frente a datos. (por ejemplo, casi todo L2 almacenará datos en caché, no código, mientras se ejecuta una multiplicación de matriz grande, en lugar de tener mucho código activo mientras se ejecuta un programa C ++ inflado, o incluso una implementación eficiente de un algoritmo complicado (por ejemplo, ejecutar gcc) ). El código se puede copiar como datos, no siempre solo se carga desde el disco a la memoria con DMA.


Los cachés también necesitan lógica para rastrear fallas pendientes (ya que la ejecución fuera de orden significa que se pueden seguir generando nuevas solicitudes antes de que se resuelva la primera falla). Tener muchas fallas pendientes significa que superpone la latencia de las fallas, logrando un mayor rendimiento. Duplicar la lógica y / o particionar estáticamente entre el código y los datos en L2 no sería bueno.

Los cachés de menor tráfico más grandes también son un buen lugar para colocar la lógica de búsqueda previa. La búsqueda previa de hardware permite un buen rendimiento para cosas como realizar un bucle en un array sin que cada fragmento de código necesite instrucciones de captación previa de software. (La captación previa de SW fue importante durante un tiempo, pero las captaciones previas de HW son más inteligentes de lo que solían ser, por lo que los consejos de Ulrich Drepper son excelentes Lo que todo programador debe saber sobre la memoria está desactualizado para muchos casos de uso).

Las memorias caché de nivel superior con poco tráfico pueden permitirse la latencia para hacer cosas inteligentes como usar un política de reemplazo adaptativa en lugar del LRU habitual. Intel IvyBridge y las CPU posteriores hacen esto, para resistir los patrones de acceso que no obtienen accesos de caché para un conjunto de trabajo un poco demasiado grande para caber en el caché. (por ejemplo, recorrer algunos datos en la misma dirección dos veces significa que probablemente sea desalojado justo antes de que se reutilice).


Un ejemplo real: Intel Haswell. Fuentes: análisis de microarquitectura de David Kanter y resultados de las pruebas de Agner Fog (microarch pdf). Consulte también los manuales de optimización de Intel (enlaces en la wiki de etiquetas x86).

Además, escribí una respuesta separada sobre: ​​¿Qué técnica de mapeo de caché se usa en el procesador intel core i7?

Los diseños modernos de Intel utilizan una gran caché L3 inclusiva compartida por todos los núcleos como respaldo para el tráfico de coherencia de la caché. Se distribuye físicamente entre los núcleos, con 2048 conjuntos * 16 vías (2MiB) por núcleo (con una política de reemplazo adaptable en IvyBridge y versiones posteriores).

Los niveles más bajos de caché son por núcleo.

  • L1: 32kiB por núcleo, cada instrucción y datos (división), asociativo de 8 vías. Latencia = 4 ciclos. Al menos 2 puertos de lectura + 1 puerto de escritura. (Tal vez incluso más puertos para manejar el tráfico entre L1 y L2, o tal vez recibir una línea de caché de L2 entra en conflicto con la retirada de una tienda). Puede rastrear 10 fallas de caché pendientes (10 búferes de relleno).
  • L2: 256kiB unificado por núcleo, asociativo de 8 vías. Latencia = 11 o 12 ciclos. Ancho de banda de lectura: 64 bytes / ciclo. La lógica de captación previa principal se capta previamente en L2. Puede rastrear 16 fallas pendientes. Puede suministrar 64B por ciclo al L1I o L1D. Se desconocen los recuentos de puertos reales.
  • L3: unificado, compartido (por todos los núcleos) 8MiB (para un i7 de cuatro núcleos). Inclusive (de todas las cachés por núcleo L2 y L1). Asociativo de 12 o 16 vías. Latencia = 34 ciclos. Actúa como un respaldo para la coherencia de la caché, por lo que los datos compartidos modificados no tienen que salir a la memoria principal y viceversa.

Otro ejemplo real: AMD Piledriver: (por ejemplo, CPUs FX de escritorio y Opteron). El tamaño de la línea de caché sigue siendo 64B, como Intel y AMD lo han utilizado durante varios años. Texto copiado principalmente del pdf de microarchivo de Agner Fog, con información adicional de algunas diapositivas que encontré, y más detalles sobre la caché de combinación de escritura L1 + 4k de escritura directa en el blog de Agner, con un comentario de que solo L1 es WT, no L2.

  • L1I: 64 kB, bidireccional, compartido entre un par de núcleos (la versión de AMD de SMD tiene más static particionamiento que Hyperthreading, y llaman a cada uno un núcleo. Cada par comparte una unidad de vector / FPU y otros recursos de canalización).
  • L1D: 16 kB, 4 vías, por núcleo. Latencia = 3-4 c. (Tenga en cuenta que los 12 bits por debajo del desplazamiento de página todavía se utilizan para el índice, por lo que el truco VIPT habitual funciona). (Rendimiento: dos operaciones por reloj, hasta una de ellas es una tienda). Política = escritura simultánea, con una caché de combinación de escritura de 4k.
  • L2: 2 MB, 16 vías, compartido entre dos núcleos. Latencia = 20 relojes. Rendimiento de lectura 1 por 4 relojes. Rendimiento de escritura 1 por reloj 12.
  • L3: 0 – 8 MB, 64 vías, compartido entre todos los núcleos. Latencia = reloj 87. Rendimiento de lectura 1 por reloj de 15. Rendimiento de escritura 1 por reloj 21

Agner Fog informa que con ambos núcleos de un par activos, el rendimiento de L1 es menor que cuando la otra mitad de un par está inactiva. No se sabe qué está pasando, ya que se supone que las cachés L1 están separadas para cada núcleo.

Aquí tienes las reseñas y valoraciones

Si guardas alguna cuestión o forma de renovar nuestro artículo eres capaz de ejecutar una ilustración y con placer lo estudiaremos.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *