Lección
La teoría — Localidad en caché: por qué importa el orden de lectura
Una caché es una memoria pequeña y rápida que guarda copias de la memoria principal en líneas. Cada línea contiene un bloque alineado de L bytes. Si el bloque al que pertenece un dato ya está en una línea, su lectura es un acierto; en caso contrario, es un fallo y se carga el bloque completo. En una caché de correspondencia directa con S líneas, el bloque que contiene la dirección a solo puede ocupar la línea ⌊a / L⌋ mod S. Un bucle que lee direcciones contiguas una tras otra aprovecha cada bloque cargado para varias lecturas. Esta propiedad se llama localidad espacial.
Qué significa cada símbolo
L- el tamaño de cada línea en bytes: aquí, 16; en la mayoría de los procesadores de sobremesa y de servidores, 64
S- el número de líneas: aquí, 8
- Supone
- Se supone una caché de correspondencia directa inicialmente vacía, enteros de 4 bytes y una matriz almacenada por filas, como en C. Fortran almacena las matrices por columnas, así que allí el orden más rápido es el contrario. Solo se cuentan las lecturas de la matriz. Las cachés reales de datos de primer nivel son mayores y asociativas por conjuntos, normalmente de 8 vías: cada bloque puede ocupar cualquiera de las 8 líneas de su conjunto. Esto elimina la mayoría de los conflictos que ves aquí, pero no resuelve la falta de espacio. Tampoco se incluye la precarga por hardware, que trae la siguiente línea antes de que se solicite.
Problema resuelto al detalle
-
¿Cuánto tráfico de memoria cuesta elegir el orden más lento? 4 pasos
Pulsa 8 × 8 por columnas y explica por qué hay 0 aciertos. Considera después una matriz de 1024 × 1024 con enteros de 4 bytes y una caché de correspondencia directa de 32 KiB con líneas de 64 bytes. ¿Cuántos bytes se transfieren desde la memoria al sumar los elementos por filas? ¿Y por columnas? ¿Ayuda añadir una línea de relleno al final de cada fila?
Abre este problema en la herramienta
-
En esta página, cada fila ocupa 8 × 4 = 32 bytes, es decir, 2 líneas. La fila i empieza en la línea 2i mod 8, y la columna 0 solo usa las líneas 0, 2, 4 y 6. a[4][0] ocupa la línea en la que estaba a[0][0]. Cuando el bucle llega a a[0][1], su bloque ya se ha expulsado: 0 aciertos de 64. Por filas hay un fallo por cada bloque de 16 bytes. Son 16 fallos y, por tanto, 48 aciertos: 1 − 4/16 = 75 %.
-
La caché grande tiene 32 768 / 64 = 512 líneas. Por filas, cada línea de 64 bytes contiene 16 enteros y solo falla la primera lectura de cada una: 1 048 576 / 16 = 65 536 fallos. La tasa de aciertos es 1 − 4/64 = 93,75 %, y se transfieren 65 536 × 64 = 4 194 304 bytes: exactamente una lectura completa de la matriz.
-
Por columnas, cada fila ocupa 4096 bytes, es decir, 64 líneas. La fila i empieza en la línea 64i mod 512, por lo que una columna solo usa 8 de las 512 líneas. a[8][0] expulsa el bloque de a[0][0], que desaparece mucho antes de que el bucle vuelva a a[0][1]. Fallan las 1 048 576 lecturas: se transfieren 67 108 864 bytes, 16 veces más.
-
Añadir 16 enteros de relleno al final de cada fila hace que ocupe 4160 bytes, es decir, 65 líneas. Como 65 y 512 son primos entre sí, cada columna usa ahora las 512 líneas. Sigue sin haber aciertos: una columna accede a 1024 bloques y la caché solo admite 512, de modo que todos se han expulsado cuando la siguiente columna los necesita.
Respuesta
4 MiB por filas, 64 MiB por columnas. El relleno distribuye los bloques de cada columna por toda la caché y aun así no se obtiene ningún acierto. Elimina los conflictos, pero los bloques de cada columna de esta matriz necesitan más espacio del que tiene la caché.
-
Ruta de aprendizaje
Cómo ejecuta un programa un ordenador
Referencias (1)
- The first insight's measured case, the line size in the definition, and the 8-way first-level caches in `assumes`: Ulrich Drepper, "What Every Programmer Should Know About Memory," Red Hat, Inc., version 1.0, 21 November 2007. §3.2 (p. 15), §3.3.1 (pp. 17–18), §6.2.1 (pp. 49–50).