2010-10-13 14 views
102

Entiendo que el procesador trae datos a la memoria caché a través de líneas de caché, lo que, por ejemplo, en mi procesador Atom, aporta aproximadamente 64 bytes a la vez, cualquiera que sea el tamaño de los datos reales que se leen.¿Cómo funcionan las líneas de caché?

Mi pregunta es:

Imagínese que usted necesita leer un byte de la memoria, que 64 bytes será reducido en la memoria caché?

Las dos posibilidades que puedo ver es que, o bien los 64 bytes comienzan en el límite de 64 bytes más cercano por debajo del byte de interés, o los 64 bytes se reparten alrededor del byte de alguna manera predeterminada (por ejemplo, medio bajo, la mitad arriba, o todo lo de arriba).

¿Qué es?

+10

Lea esto: [Lo que todo programador debería saber sobre la memoria] (http://lwn.net/Articles/250967/). Luego léelo nuevamente. Mejor (pdf) [fuente aquí] (http://www.akkadia.org/drepper/cpumemory.pdf). – andersoj

+3

Esto también tiene bastante buena información relacionada con su consulta: http://www.cs.umd.edu/class/sum2003/cmsc311/Notes/Memory/introCache.html –

Respuesta

85

Si la línea de caché que contiene el byte o la palabra que está cargando no está presente en la memoria caché, solicitará los 64 bytes que comienzan en el límite de la línea de caché (la dirección más grande debajo de la que necesita es múltiplo de 64).

Los módulos de memoria de PC modernos transfieren 64 bits (8 bytes) a la vez, in a burst of eight transfers, por lo que un comando desencadena una lectura o escritura de una línea de memoria caché completa. (DDR1/2/3/4 SDRAM tamaño de transferencia de ráfaga es configurable hasta 64B; CPUs seleccionarán el tamaño de transferencia de ráfaga para que coincida con su tamaño de línea de caché, pero 64B es común)

Como regla general, si el procesador no puede pronosticar el acceso a la memoria (y recuperarla previamente), el proceso de recuperación puede tomar ~ 90 nanosegundos, o ~ 250 ciclos de reloj (desde que la CPU conoce la dirección a la CPU que recibe los datos).

Por el contrario, un golpe en caché L1 tiene una latencia de uso de carga de 3 o 4 ciclos, y una recarga de tienda tiene una latencia de reenvío de tienda de 4 o 5 ciclos en las modernas CPU x86. Las cosas son similares en otras arquitecturas.

Lectura adicional: What Every Programmer Should Know About Memory de Ulrich Drepper. El consejo de captación previa del software está un poco desactualizado: los precapitores HW modernos son más inteligentes, y el hyperthreading es mucho mejor que en los días P4 (por lo que un hilo de captación previa suele ser un desperdicio). Además, la wiki de la etiqueta tiene muchos enlaces de rendimiento para esa arquitectura.

+0

¿Por qué no hay un bus de datos de 64 bytes de ancho que permita línea de caché a la vez? Resulta realmente ineficiente hacer esto en pasos de 8 bytes. – Mike76

+0

Esta respuesta no tiene ningún sentido. ¿Qué tiene que ver con el ancho de banda de memoria de 64 bits (que también es incorrecto en ese sentido) con el byte de 64 bytes (!)? También las 10 a 30 ns también son totalmente incorrectas si golpeas el Ram. Puede ser cierto para la memoria caché L3 o L2, pero no para la memoria RAM, donde es más como 90ns. Lo que quiere decir es el tiempo de ráfaga: el tiempo para acceder a la siguiente palabra cuádruple en modo ráfaga (que en realidad es la respuesta correcta) –

+2

@MartinKersten: Un canal de DDR1/2/3/4 SDRAM utiliza datos de 64 bits Ancho de bus. Una transferencia en ráfaga de una línea de caché completa requiere ocho transferencias de 8B cada una, y es lo que realmente ocurre. Todavía podría ser correcto que el proceso se optimice transfiriendo primero el fragmento alineado con 8B que contiene el byte deseado, es decir, comenzando el estallido allí (y envolviéndolo si no fue el primer 8B del tamaño de transferencia de ráfaga). Sin embargo, las CPU modernas con cachés de varios niveles probablemente ya no lo hagan porque significaría retransmitir los primeros bloques de la ráfaga hasta la memoria caché L1 con anticipación. –

6

Los procesadores pueden tener cachés de varios niveles (L1, L2, L3), y estos difieren en tamaño y velocidad.

Sin embargo, para comprender qué es exactamente lo que entra en cada caché, tendrá que estudiar el predictor de bifurcación utilizado por ese procesador específico y cómo las instrucciones/datos de su programa se comportan en su contra.

Lea sobre branch predictor, CPU cache y replacement policies.

Esta no es una tarea fácil. Si al final del día todo lo que quiere es una prueba de rendimiento, puede usar una herramienta como Cachegrind. Sin embargo, como se trata de una simulación, su resultado puede diferir en algún grado.

4

No puedo decirlo con certeza ya que cada hardware es diferente, pero por lo general "64 bytes comienzan en el límite de 64 bytes más cercano", ya que es una operación muy rápida y simple para la CPU.

+2

I * puedo * decir con certeza. Cualquier diseño de memoria caché razonable tendrá líneas con tamaños que son una potencia de 2 y que están naturalmente alineados. (por ejemplo, alineado con 64B). ** No solo es rápido y simple, es literalmente gratis: simplemente ignoras los 6 bits bajos de la dirección, por ejemplo. ** Los cachés a menudo hacen cosas diferentes con diferentes rangos de direcciones. (p. ej., el caché se preocupa por la etiqueta y el índice para detectar aciertos y errores, y luego solo utiliza el desplazamiento dentro de una línea de caché para insertar/extraer datos) –

16

Si las líneas de caché tienen 64 bytes de ancho, corresponden a bloques de memoria que comienzan en direcciones divisibles por 64. Los 6 bits menos significativos de cualquier dirección son un desplazamiento a la línea de caché.

Así que para cualquier byte dado, la línea de caché que tiene que ser recuperado se puede encontrar en la limpieza de las menos signficant seis bits de la dirección, que corresponde al redondeo hacia abajo a la dirección más cercana que es divisible por 64.

Aunque esto se hace por el hardware, podemos mostrar los cálculos utilizando algunas definiciones macro de C de referencia:

#define CACHE_BLOCK_BITS 6 
#define CACHE_BLOCK_SIZE (1U << CACHE_BLOCK_BITS) /* 64 */ 
#define CACHE_BLOCK_MASK (CACHE_BLOCK_SIZE - 1) /* 63, 0x3F */ 

/* Which byte offset in its cache block does this address reference? */ 
#define CACHE_BLOCK_OFFSET(ADDR) ((ADDR) & CACHE_BLOCK_MASK) 

/* Address of 64 byte block brought into the cache when ADDR accessed */ 
#define CACHE_BLOCK_ALIGNED_ADDR(ADDR) ((ADDR) & ~CACHE_BLOCK_MASK) 
+1

Tengo dificultades para entender esto. Sé que es 2 años después, pero ¿me pueden dar un código de ejemplo para esto? una o dos líneas – Nick

+1

@Nick ilustrado con el código C. – Kaz

+0

@Nick El motivo por el que funciona este método reside en el sistema de números binarios. Cualquier potencia de 2 solo tiene un bit configurado y todos los bits restantes se borran, entonces para 64, tienes '0b1000000', fíjate que los últimos 6 dígitos son ceros, incluso cuando tienes algún número con cualquiera de esos 6 conjuntos (que representan el número% 64), borrarlos le dará la dirección de memoria alineada de 64 bytes más cercana. – legends2k

8

primer lugar un acceso a la memoria principal es muy caro. Actualmente, una CPU de 2 GHz (la más lenta una vez) tiene 2G tics (ciclos) por segundo. Una CPU (núcleo virtual hoy en día) puede recuperar un valor de sus registros una vez por marca. Dado que un núcleo virtual se compone de múltiples unidades de procesamiento (ALU - unidad lógica aritmética, FPU, etc.), de hecho puede procesar ciertas instrucciones en paralelo si es posible.

El acceso a la memoria principal cuesta entre 70 y 100ns (DDR4 es un poco más rápido). Esta vez básicamente busca la caché L1, L2 y L3 y luego toca la memoria (envía el comando al controlador de memoria, que lo envía a los bancos de memoria), espera la respuesta y finaliza.

100ns significa aproximadamente 200 tics. Entonces, básicamente, si un programa siempre pierde los cachés a los que accede cada memoria, la CPU gastaría aproximadamente el 99,5% de su tiempo (si solo lee memoria) en espera para la memoria.

Para acelerar las cosas hay cachés L1, L2, L3. Utilizan la memoria que se coloca directamente en el chip y el uso de un tipo diferente de circuitos de transistores para almacenar los bits dados. Esto requiere más espacio, más energía y es más costoso que la memoria principal ya que una CPU generalmente se produce usando una tecnología más avanzada y una falla de producción en la memoria L1, L2, L3 tiene la posibilidad de inutilizar la CPU (defecto) grandes cachés L1, L2, L3 aumentan la tasa de error que disminuye el rendimiento que directamente disminuye el ROI. Entonces, hay una gran compensación cuando se trata del tamaño de caché disponible.

(actualmente uno crea más cachés L1, L2, L3 para poder desactivar ciertas porciones para disminuir la posibilidad de que un defecto de producción real sea que las áreas de memoria caché vuelvan a la CPU defectuosa).

Para dar una idea de temporización (fuente: costs to access caches and memory)

  • caché L1: 1ns a 2ns (2-4 ciclos)
  • caché L2: 3NS a 5ns (6-10 ciclos)
  • caché L3: 12ns a 20ns (24-40 ciclos)
  • RAM: 60 ns (120 ciclos)

Ya que mezclar diferentes tipos de CPU estos son sólo estimaciones, pero darle una oportunidad od idea qué va realmente cuando se recupera un valor de memoria y podemos tener un golpe o una falla en cierta capa de caché.

Por lo tanto, una memoria caché básicamente acelera mucho el acceso a la memoria (60ns frente a 1ns).

Obteniendo un valor, almacenarlo en la memoria caché para la posibilidad de volver a leerlo es bueno para las variables a las que se accede a menudo pero para las operaciones de copia de memoria aún sería lento ya que uno solo lee un valor, escribe el valor en alguna parte nunca vuelve a leer el valor ... no hay hits de caché, dead slow (además de esto puede suceder en paralelo ya que tenemos ejecución fuera de servicio).

Esta copia de la memoria es tan importante que existen diferentes medios para acelerarla.En los primeros días, la memoria a menudo podía copiar memoria fuera de la CPU. Fue manejado por el controlador de memoria directamente, por lo que una operación de copia de memoria no contaminó las memorias caché.

Pero además de una copia de memoria normal, otro acceso serial de memoria era bastante común. Un ejemplo es analizar una serie de información. Tener una matriz de enteros y calcular suma, media, promedio o incluso más simple, encontrar un cierto valor (filtro/búsqueda) era otra clase muy importante de algoritmos que se ejecutaban cada vez en cualquier CPU de propósito general.

Analizando el patrón de acceso a la memoria, fue evidente que los datos se leen secuencialmente con mucha frecuencia. Había una alta probabilidad de que si un programa lee el valor en el índice i, el programa también leerá el valor i + 1. Esta probabilidad es ligeramente mayor que la probabilidad de que el mismo programa también lea el valor i + 2 y así sucesivamente.

Así que al darle una dirección de memoria era (y sigue siendo) una buena idea leer más adelante y buscar valores adicionales. Esta es la razón por la cual hay un modo boost.

El acceso a la memoria en modo refuerzo significa que se envía una dirección y se envían múltiples valores secuencialmente. Cada envío de valor adicional solo requiere 10ns adicionales (o incluso menos).

Otro problema era una dirección. Enviar una dirección lleva tiempo. Para abordar una gran parte de la memoria, se deben enviar direcciones grandes. En los primeros días significaba que el bus de direcciones no era lo suficientemente grande como para enviar la dirección en un solo ciclo (marca) y se necesitaba más de un ciclo para enviar la dirección y agregar más retraso.

Una línea de caché de 64 bytes, por ejemplo, significa que la memoria está dividida en bloques de memoria distintos (no superpuestos) de 64 bytes de tamaño. 64bytes significan que la dirección de inicio de cada bloque tiene los seis bits de dirección más bajos para que siempre sean ceros. Por lo tanto, no es necesario enviar estos seis bits cero cada vez, aumentando el espacio de direcciones 64 veces para cualquier número de ancho de bus de dirección (efecto de bienvenida).

Otro problema que resuelve la línea de caché (además de leer y guardar/liberar seis bits en el bus de direcciones) está en la forma en que se organiza el caché. Por ejemplo, si un caché se divide en bloques de 8 bytes (64 bits) (celdas), se necesita almacenar la dirección de la celda de memoria con la que esta celda guarda el valor. Si la dirección también sería de 64 bits, esto significa que la dirección consumirá la mitad del tamaño del caché, lo que generará una sobrecarga del 100%. Dado que una línea de caché es de 64bytes y una CPU puede usar 64bit - 6bit = 58bit (no es necesario almacenar los bits cero demasiado a la derecha) significa que podemos guardar en caché 64bytes o 512bits con una sobrecarga de 58bit (11% de sobrecarga). En realidad, las direcciones almacenadas son incluso más pequeñas que esto, pero hay información de estado (como la línea de caché válida y precisa, sucia y necesita escribir nuevamente en ram, etc.).

Otro aspecto es que tenemos caché set-associative. No todas las celdas de caché pueden almacenar una dirección determinada, sino solo un subconjunto de ellas. Esto hace que los bits de dirección almacenados necesarios sean aún más pequeños, permite el acceso paralelo de la memoria caché (se puede acceder a cada subconjunto una vez, pero de forma independiente de los otros subconjuntos).

Hay más especialmente cuando se trata de sincronizar el acceso de caché/memoria entre los diferentes núcleos virtuales, sus múltiples unidades de procesamiento independientes por núcleo y finalmente múltiples procesadores en una placa base (que contienen placas que incluyen hasta 48 procesadores y más)

Esta es básicamente la idea actual de por qué tenemos líneas de caché. El beneficio de leer por adelantado es muy alto y el peor caso de leer un solo byte de una línea de caché y nunca volver a leer el resto es muy pequeño ya que la probabilidad es muy pequeña.

El tamaño de la línea de caché (64) es una opción sabia entre las líneas de caché más grandes hace que sea poco probable que el último byte sea leído también en un futuro próximo, la duración que tarda en llegar la línea de caché completa de la memoria (y para volver a escribirla) y también la sobrecarga en la organización de caché y la paralelización de la memoria caché y el acceso a la memoria.

+1

Una caché de conjunto asociativo utiliza algunos bits de dirección para seleccionar un conjunto, por lo que las etiquetas pueden ser incluso más cortas que su ejemplo. Por supuesto, la memoria caché también necesita hacer un seguimiento de qué etiqueta va con qué matriz de datos en el conjunto, pero generalmente hay más conjuntos que formas dentro de un conjunto. (por ejemplo, caché L1D asociativa de 8 kb de 32kB, con líneas de 64B, en procesadores Intel x86: desplazamiento de 6 bits, índice de 6 bits. Las etiquetas solo deben tener 48-12 bits de ancho, ya que x86-64 (por ahora) solo tiene 48- Como estoy seguro, ya sabes, no es una coincidencia que los 12 bits bajos sean el desplazamiento de página, por lo que L1 puede ser VIPT sin aliasing.) –

+0

respuesta increíble brote ... hay un botón "me gusta" en cualquier lugar ? –

Cuestiones relacionadas