Documentos de Académico
Documentos de Profesional
Documentos de Cultura
Capítulo 4 PDF
Capítulo 4 PDF
Para que un procesador pueda procesar las instrucciones del programa a pleno rendimiento
es preciso que la memoria proporcione las instrucciones y los datos cuando son necesarios.
Por ejemplo, si un procesador es capaz de captar 4 instrucciones por ciclo y funciona a una
frecuencia de 1 GHz (1 ns de tiempo de ciclo), con un tamaño de palabra de 32 bits, el
ancho de banda de memoria que se necesita para mantener este ritmo (sólo para la lectura de
las instrucciones) es de:
L instrucción
Binstruciones Tinstrucción IPC F 4 4 109 bytes/s 16 Gbytes/s (1)
CPI TCPU
asumiendo que la longitud de cada dato es Tdato = 4 bytes. Es decir que se necesitaría un
ancho de banda de unos
Teniendo en cuenta que una memoria principal con tecnología DDR2 y bus de 800 MHz
puede proporcionar del orden de BMem = 12 Gbytes/s, es fundamental disponer de una
jerarquía de memoria eficiente en la que el procesador encuentre los datos y las instrucciones
que necesita en una cache suficientemente rápida.
Las prestaciones de la memoria se caracterizan a partir del tiempo de latencia y del ancho de
banda (bits por unidad de tiempo). El tiempo de latencia es el retardo entre que se solicita
acceder a una palabra de memoria y se termina dicho acceso, bien porque se tiene la palabra
en el caso de una lectura, bien porque se ha actualizado la palabra en memoria en el caso de
una escritura. El ancho de banda mide el número de palabras por unidad de tiempo que
puede proporcionar la memoria (o que pueden escribirse en la memoria) una vez se ha
accedido a la primera. Cuando el procesador necesita acceder a un dato solicitado al ejecutar
una instrucción de carga, la magnitud que hay que tener en cuenta es la latencia de memoria.
Si de lo que se trata es de acceder a un bloque de memoria principal que pasa a cache,
interesa tener en cuenta el ancho de banda.
Figura 1. Campos para los esquemas de correspondencia entre las direcciones de memoria principal y
cache.
El tiempo medio de acceso a memoria en una jerarquía con dos niveles, T2, en la que el
primer nivel corresponde a la cache L1, viene dado por la expresión:
T2 a 1 t 1 1 a 1 t 1 t M (4)
donde a1 es la tasa de aciertos a cache (la probabilidad de que un acceso a memoria esté en
cache), t1 es el tiempo de acceso a la cache de primer nivel, y tM el tiempo de acceso a la
memoria principal. En la expresión anterior se está suponiendo que todos los accesos se
producen en primer lugar a cache y cuando la palabra no está ahí, se genera el acceso a
memoria principal (cache look-through). En el caso de que se tenga una jerarquía de tres
niveles (caches L1 y L2 y memoria principal), la expresión para el tiempo medio de acceso a
memoria es:
T3 a 1 t 1 1 a 1 a 2 t 1 t 2 1 a 1 1 a 2 t 1 t 2 t M (5)
t2
a2 (6)
tM
Esta condición establece la tasa de aciertos del nivel que se introduzca en términos de los
tiempos de acceso de ese nivel y el nivel inferior. Teniendo en cuenta estas expresiones para
mejorar las prestaciones de la jerarquía de memoria, se pueden mejorar los tiempos de acceso
a los distintos niveles, reducir la tasa de fallos a los distintos niveles de cache, y/o reducir la
penalización cuando se producen fallos. Entre las técnicas para reducir la tasa de fallos, junto
a técnicas basadas en la inclusión de recursos como las caches de víctimas o las pseudo-
asociativas, también existen procedimientos que se pueden aplicar desde el nivel de
programación, tales como las técnicas de precaptación (que también requieren o pueden
beneficiarse de ciertos recursos en el procesador) y otras técnicas de optimización de código
como la mezcla de arrays, la fusión de bucles, o las operaciones con submatrices (blocking).
Existen bastantes textos donde se pueden consultar detalles de estas técnicas, entre ellos
[ORT05].
En las expresiones para el cálculo del tiempo medio de acceso al sistema de memoria no se
ha tenido en cuenta el tiempo que se consume cuando, además de reemplazar una línea de
cache con información que se trae desde memoria principal, hay que actualizar en memoria
principal el contenido de la línea reemplazada (que el procesador ha modificado con respecto
a lo que se captó de memoria principal en su momento). Así, en una jerarquía de memoria de
dos niveles con una cache L1 con post-escritura (se actualiza la información modificada en
cache en el momento del reemplazo de la línea) el tiempo medio de acceso será:
T2 a1 t 1 1 a1 t 1 t M preeemplazo t línea (7)
donde preemplazo es la probabilidad de que cuando se produzca una falta haya que reemplazar la
línea actualizando en memoria la línea reemplazada, y tlínea es el tiempo necesario para
actualizar la línea.
0 1 1 0
Decodificador
1 1 1 0
de fila
0 1 0 1
0 0 0 1
1 1 1 0 1
Decodificador
de columna
En la Figura 2 se puede observar cómo se realiza el acceso a una memoria DRAM. Como
este tipo de memoria está organizada en matrices de celdas de memoria, el controlador de
memoria generará dos direcciones, una de fila y otra de columna. En un primer acceso a la
matriz se seleccionará la fila de celdas en la que esté el bit al que se quiere acceder, y
posteriormente, en un segundo acceso se accederá a la columna correspondiente para
acceder al bit deseado. A través de los años han aparecido diversas arquitecturas de memoria
DRAM para configurar la memoria principal (FPM, EDO, BEDO, SDRAM, DDR,
DDR2,…). La mejora de prestaciones que ofrecen está relacionada fundamentalmente con el
ancho de banda que proporcionan los circuitos de memoria más que con los tiempo de
acceso, que disminuyen mucho más lentamente y a un ritmo menor que la velocidad de los
procesadores. En la Figura 3 se muestra un esquema que resume el fundamento en que se
basa la mejora en el ancho de banda de las arquitecturas de memoria DRAM que van
apareciendo. La clave está en aprovechar la localidad de los accesos a memoria principal, ya
que, dado que se dispone de caches, usualmente se accede a memoria para leer o escribir, no
una palabra, sino una línea de cache completa cuyos elementos están en posiciones
contiguas. Otra mejora importante consiste en modificar el diseño de la circuitería de la
interfaz del circuito de memoria para poder segmentar estas lecturas cada vez más
eficientemente.
Acceso convencional 1 1 2 2 3 3 4 4
FPM 1 1 2 3 4 5 5 6 7 8
EDO 1 1 2 3 4 5 5 6 7 8
BEDO 1 1 2 3 4
5 5 6 7 8
Las señales de control que deben intercambiarse con las memorias DRAM (las direcciones
de fila y de columna de las celdas de memoria, las señales que validan esas direcciones, las
que permiten controlar el refresco de las celdas de memoria, etc.) son generadas por el
controlador de memoria, que actualmente se encuentra integrado dentro del puente norte del
chipset. El controlador de memoria genera las señales para la DRAM a partir de las que
recibe del procesador (o de cualquier otro elemento que pueda realizar accesos a la memoria
principal). Por tanto, una transferencia de datos entre el procesador y la memoria (el
controlador de memoria) se lleva a cabo intercambiando una serie de señales de datos,
direcciones y control a lo largo de uno o varios ciclos de reloj. El conjunto de ciclos
necesario para realizar una transferencia entre el procesador y la memoria (entre el
procesador y el controlador de memoria que, a su vez se tiene que comunicar con el propio
circuito de memoria) se denomina ciclo de bus. Usualmente, un ciclo de bus comprende varios
ciclos de reloj, y ese número puede ser variable en función de que la memoria (a través del
controlador de memoria) tarde más o menos tiempo en completar la transacción que el
procesador solicita (proporcionar el dato en el caso de una escritura, o terminar la escritura
del dato en las celdas de memoria correspondientes). Usualmente, el procesador incorpora
diferentes tipos de ciclos de bus para mejorar el ancho de banda de intercambio de
información con la memoria. Así, dado que en sistemas con cache, el acceso a la memoria
externa suele implicar el acceso a varias palabras de memoria principal para leer o escribir
una línea de cache, existen ciclos de bus específicos, como los ciclos burst o a ráfagas, en los
que el procesador sólo genera la dirección de una palabra dentro de la línea, e indica, con las
correspondientes señales de control, que desea todas las palabras de la línea en la que se
incluye la dirección que ha proporcionado. El controlador de memoria se encarga de generar
las señales para que la DRAM proporcione esos datos. Como, por otra parte, se trata de
palabras que estarán en posiciones contiguas de memoria, se aprovechará eficientemente el
diseño de la arquitectura de memoria que favorece los accesos a posiciones consecutivas, tal
y como se ha visto antes.
Así por ejemplo, si un ciclo de acceso a memoria necesita un mínimo de Nciclos ciclos de reloj,
el máximo ancho de banda que podrá conseguirse sería de:
N bits Fbus
B (8)
N ciclos
donde Nbits es número de bits que tiene el bus de datos de la interfaz de memoria del
procesador, y Fbus es la frecuencia en ciclos/s. En el caso de una transferencia en modo burst,
si una línea de cache necesita Naccesos_línea accesos a memoria, el primero de ellos sí necesitará
un mínimo de Nciclos ciclos, pero los restantes Naccesos_línea – 1 accesos sí podrían realizarse en
un ciclo (si la memoria es lo suficientemente rápida para seguir ese ritmo). Por lo tanto, se
tendría un ancho de banda igual a:
que es mayor que el ancho de banda que obtendríamos si tuviéramos que acceder palabra a
palabra a toda la información de la línea. Como puede verse, si hay que acceder a varias
líneas, a medida que Naccesos_línea se hace mayor, el ancho de banda del bus tiende a
Nbits × Fbus, que es el máximo ancho de banda que se podría conseguir para la frecuencia y
anchura del bus de datos disponible. Esto se puede conseguir aplicando segmentación
(address pipelining) a la hora de acceder a memoria, de forma que se puedan recibir los datos de
la petición de memoria actual mientras se manda la dirección para la siguiente lectura
[ORT05].
Problemas
1. En un computador el bus de memoria principal es de 100 MHz y 64 bits de datos.
a) Si el ciclo de lectura del bus necesita dos ciclos de reloj, ¿cuál sería el tiempo
máximo de acceso que debería tener la memoria para que no se necesiten
introducir estados de espera?
b) Suponga que quiere cargar desde la memoria una línea de cache constituida
por 64 bytes ¿Cuál es la ganancia en ancho de banda que se obtiene si se
utiliza acceso en modo burst con relación a acceso palabra a palabra si el
tiempo de acceso de la memoria es de 45 ns?
Solución
Dado que el tiempo de ciclo de lectura del bus necesita dos ciclos de reloj, el tiempo máximo de
acceso para no tener que introducir estados de espera en una lectura, es igual a:
N ciclos_lectura 2
Taccesomax N ciclos_lectura Tciclo_bus 20 ns
Fbus 100 106
Como en el enunciado nos dicen que el tiempo de acceso a la memoria es de 45 ns, será necesario
introducir 3 ciclos de espera en el bus de memoria para esperar a que la memoria nos devuelva los
datos solicitados. Por tanto, el tiempo mínimo de acceso a la memoria del enunciado será de 5 ciclos
de bus, ya que
N ciclos 1 5 1 N 5
40 ns Tmem 45 ns ciclos 50 ns
Fbus 100 106 Fbus 100 106
Puesto que el bus tiene 64 bits de datos, es decir 8 bytes, para acceder a 64 bytes se necesitarían 8
accesos a memoria, por tanto, el tiempo necesario para realizar todas las lecturas sería de:
Si se puede utilizar un modo burst que permita leer una línea completa de 64 bytes para introducirla en
la cache, se tendría que el número de ciclos necesario es 5-1-1-1-1-1-1-1 (cinco ciclos para acceder a la
primera palabra, y luego un ciclo para acceder a cada una de las restantes). Por tanto, el tiempo
necesario para realizar la lectura sería igual al tiempo de lectura de una ráfaga:
N ciclos_burst 5 1 1 1 1 1 1 1
Tburst 120 ns
Fbus 100 106
Una vez obtenidos estos tiempos, se puede calcular la ganancia en velocidad como:
Tburst 400
S 3.33
T 120
Solución
Teniendo en cuenta que el tamaño de la memoria RAM es de 32 Mbytes (225 bytes) y de la cache es de
64 Kbytes (216 bytes), será necesario usar direcciones de 25 y 16 bits, respectivamente, para referirse a
una dirección en cada una de estas memorias. Por otro lado, como las líneas de cache son de 128 bytes
(27 bytes), la cache tendrá un total de 512 líneas (29 líneas):
Si la memoria cache es de correspondencia directa, los 7 bits menos significativos de una dirección de
memoria RAM indicarán la posición de un byte dentro de una línea (esto es así en cualquier
organización de cache, puesto que, al transferirse líneas completas entre cache y memoria, la
correspondencia se establece entre líneas), los siguientes 9 bits indicarán la línea de cache en donde se
almacena la línea de memoria principal, y los restantes 9 se usarán como campo marca para poder
identificar la línea concreta de memoria principal que se ha introducido en la línea de cache en
cuestión. En la Figura 4 se esquematiza la correspondencia entre los bits de las direcciones de
memoria RAM y de la cache.
Figura 4. Correspondencia entre las direcciones de memoria RAM y las direcciones de la cache con
correspondencia directa del problema 2.
Así pues, un byte situado en la dirección de memoria 0x0110ab48 estará situado en la dirección 0xab48
de la cache, tal y como indica la Tabla 1.
BINARIO 1 0001 0000 1010 1011 0100 1000 1 0001 0000 1 0101 0110 100 1000
HEXADECIMAL 0x0110ab48 0x110 0xab48
Si la memoria cache fuese asociativa de 4 vías, las líneas estarían agrupadas en conjuntos de cuatro,
por lo que el número de conjuntos sería 29 / 22 = 27 conjuntos. Así, al establecerse la correspondencia
entre líneas de memoria y conjuntos de cache, se necesitarían 7 bits de memoria principal para indicar
el conjunto, y se introducirían 11 bits (los 11 bits más significativos) en el campo de marca, tal y como
indica la Figura 5. Para completar la dirección de cache faltarían 2 bits (4 vías = 22), que se determinan
según la política de asignación que se implemente en la memoria asociativa donde se introducen los
campos de marca.
11 bits 7 bits 7 bits
7 bits 7 bits
2 bits
Figura 5. Correspondencia entre las direcciones de memoria RAM y las direcciones de la cache
asociativa de 4 vías del problema 2.
Así pues, a partir de la dirección de memoria 0x0110ab48 se obtendrían la marca, conjunto, vía y
desplazamiento indicados en la Tabla 2. Como a priori no podemos saber en qué línea del conjunto se
alojará el dato, los dos bits para indicar la vía se han marcado como XX. Si, por ejemplo se le asigna la
línea 1 del conjunto, el valor de esos bits sería 01 y tendríamos que la dirección del dato dentro de la
cache sería 1010110 01 1001000, o 0xacc8 en hexadecimal.
BINARIO 1 0001 0000 1010 1011 0100 1000 100 0100 0010 101 0110 XX 100 1000
HEXADECIMAL 0x0110ab48 0x442 0xa??8
Tabla 2. Marca, conjunto y desplazamiento dentro de la línea de la dirección de memoria del problema 2
en la cache con correspondencia directa.
Solución
Como la memoria RAM tiene un tamaño de 64 Mbytes (226 bytes), serán necesarios 26 bits para
codificar las direcciones. En cuanto a la cache L1, al tener un tamaño de 16 Kbytes (214 bytes) y líneas
de 32 bytes (32 = 25), cada una de sus líneas se direccionará mediante 14 bits, de los cuales, los 5
menos significativos indicarán el desplazamiento del dato dentro de la línea. Si dividimos el tamaño de
la cache L1 entre el tamaño de cada línea, obtendremos el número de líneas que hay en la cache:
Una vez calculado el número de líneas, el número de conjuntos se obtiene con la siguiente expresión:
N líneas_cache_L1 29
N conjuntos_cache_L1 28 256
N vías_conjunto 2
De esta forma, los5 bits menos significativos corresponden a la posición de byte dentro de una línea,
los 8 bits siguientes indican el conjunto en el que se introduce la línea, y el resto de los bits más
significativos se usarán como marca, como indica la Figura 6.
13 bits 8 bits 5 bits
Figura 6. Correspondencia entre las direcciones de memoria RAM y las direcciones de la cache L1 del
problema 3.
Así, en la posición de memoria 0x0012aac4 se usarán los 5 bits menos significativos para direccionar el
dato dentro de la línea y los siguientes 8 bits para determinar el conjunto. Como los conjuntos son de
dos vías y no podemos saber a cuál de las dos se asignará la línea de memoria, no podemos determinar
el bit 5 de la dirección de cache, tal y como muestra la Tabla 3, por tanto, la dirección en cache será
01010110 X 00100 donde el valor de X se fijará en el momento en que se introduzca la línea en cache,
según el estado de ocupación de las dos líneas del conjunto y de la política de asignación. Si
suponemos que se le asigna el conjunto 0, la dirección será 01010110 0 00100, 0x1584 en
hexadecimal.
BINARIO 00 0001 0010 1010 1010 1100 0100 0 0000 1001 0101 0101 0110 X 0 0100
HEXADECIMAL 0x0012aac4 0x0095 0x15?4
Si nos fijamos ahora en la cache L2, como tiene un tamaño de 256 Kbytes (218 bytes) y líneas de 32
bytes (25), usando el mismo razonamiento de más arriba podemos concluir que está formada por 2 11
conjuntos de 4 vías, por lo que de los 26 bits de una dirección de memoria RAM, se usarán los 5
menos significativos como desplazamiento dentro de las líneas de la cache, los siguientes 11 bits para
determinar el conjunto, y el resto como marca, tal y como indica la Figura 7.
11 bits 5 bits
2 bits
Figura 7. Correspondencia entre las direcciones de memoria RAM y las direcciones de la cache L2 del
problema 3.
La Tabla 4 muestra cómo se obtienen los campos de marca, conjunto y desplazamiento de la dirección
en la cache L2. Como no sabemos a cuál de las 4 líneas del conjunto se asignará la dirección, tenemos
que concluir que la dirección en la cache será 10101010110 XX 00100, donde el valor de XX se fijará
en el momento en que se introduzca la línea en cache, según el estado de ocupación de las dos líneas
del conjunto y de la política de asignación. Si suponemos que se le asigna el conjunto 00 se tiene que la
dirección será 10 1010 1011 0000 0100, 0x2ab04 en hexadecimal (con 18 bits).
REPRESENTACIÓN DIRECCIÓN RAM MARCA CONJUNTO VÍA DESP.
BINARIO 00 0001 0010 1010 1010 1100 0100 00 0001 0010 101 0101 0110 XX 0 0100
HEXADECIMAL 0x0012aac4 0x012 0x2ab?4
Solución
El tiempo medio de acceso al sistema de memoria constituido por dos niveles (la memoria cache
interna L1 y la memoria principal DRAM) es igual a:
donde a1 es la tasa de aciertos en la cache L1, t1 es el tiempo de acceso a la memoria cache L1, y tM el
de la memoria principal. Se ha supuesto que el dato buscado siempre está en la memoria principal y
que la memoria cache es del tipo look-through.
En el caso de un sistema de memoria con tres niveles, en el que se añade una memoria implementada
con celdas SRAM que constituye el nivel de cache externa L2, se tiene que, si a2 es la tasa de aciertos
de la memoria cache L2 (que se supone también de tipo look-through), el tiempo medio de acceso es
igual a:
T3 a 1 t 1 1 a 1 a 2 t 1 t 2 1 a 1 1 a 2 t 1 t 2 t M
0.8 2.5 1 0.8 a 2 2.5 5 1 0.8 1 a 2 2.5 5 10 5.5 2 a 2
5.5 2 a 2 4.5
es decir, que a2 > 0.5, o lo que es lo mismo, que la memoria cache L2 tiene que tener una tasa de
aciertos mayor del 50%.
Para responder al segundo apartado, teniendo en cuenta que T3 = 5.5 – 2 × a2, si se quiere que T3 sea
igual a 3.7 se debe cumplir que
T3 5.5 2 a 2 3.7
Por último, para responder al tercer apartado, el mínimo tiempo de acceso medio se correspondería a
la situación en la que a2 = 1 (siempre que se busca un dato en L2 se encuentra allí):
T3 5.5 2 a 2 5.5 2 1 3.5 ns
Este tiempo es el mismo que se obtendría si en la expresión del cálculo de T2 (para el sistema de
memoria de dos niveles) se hiciera el tiempo de acceso a la DRAM igual al tiempo de acceso a la
memoria cache L2.
Solución
Empezaremos situando las líneas del programa en las líneas de la cache interna para instrucciones para
determinar los fallos de cache que se producen al acceder a las instrucciones. Como la cache de
instrucciones tiene 4 Kbytes (212 bytes), con líneas de 32 bytes (25 bytes), habrá 212 / 25 = 128 líneas, y
como las líneas se agrupan en conjuntos de dos líneas, tendremos 128 / 2 = 64 (26) conjuntos. Por lo
tanto, los bits 0 – 4 indican el byte dentro de la línea, y los bits 5 – 10 el conjunto al que van las líneas
de memoria principal.
Para determinar el número de fallos en la cache de instrucciones hay que tener en cuenta que el bucle
tiene 8 Kbytes, y que al empezar en la posición 0x0000, las líneas se empiezan a cargar en cache desde
el conjunto 0 (los bits 5 – 10 son 0). Así, los primeros 4 Kbytes (128 líneas) se pueden situar en los 64
conjuntos de dos vías de la cache ocasionando 128 faltas. Las líneas de los restantes 4 Kbytes del bucle
tendrán que ir reemplazando las líneas ya cargadas, ocasionando otros 128 fallos. En total, la iteración
del bucle ocasionará 256 fallos.
Estos 256 fallos se generarán en cada una de las 30 iteraciones puesto que los primeros 4 Kbytes de
cada iteración tienen que situarse en las líneas de cache ocupadas por los últimos 4 Kbytes de la
iteración anterior, etc. Por lo tanto, el número de fallos en el bucle es:
N fallos_final 128
Para calcular la tasa de fallos también es necesario conocer el número de instrucciones que se han
ejecutado:
donde Tbucle, Tfinal y Tinstrucción son respectivamente los tamaños del cuerpo del bucle, del final del
programa y de una instrucción en bytes. Por lo tanto, la tasa de fallos en la cache L1 de instrucciones
es:
N fallos_programa 7808
f I1 0.125
NI 62462
y la tasa de aciertos:
a I 1 1 f I 1 1 0.125 0.875
Como se producen 500 fallos en el acceso a la cache de datos, la tasa de fallos es de:
N fallos_datos 500
f D1 0.013
ND 37477
y la tasa de aciertos:
a D1 1 f D1 1 0.013 0.987
Para calcular el tiempo medio de acceso a la memoria de datos hay que tener en cuenta el tiempo de
acceso a cache, y a la memoria principal en el caso de fallo de cache. La expresión es:
TD a D1 t D1 1 a D1 t D1 t MD p reemplazo t línea 0.987 5 1 0.987 5 50 0.2 80 5.793 ns
donde preemplazo es la frecuencia con la que se producen reemplazos de línea (20%) y t línea es el tiempo
que se tarda en transferir la línea a la memoria principal mediante una ráfaga ((5 + 1 + 1 + 1) × 10 ns).
6. En un computador, el bus tiene una frecuencia de 200 MHz, y la memoria principal un
tiempo de acceso de 30 ns, que, tras el primer acceso, puede proporcionar (o recibir) una
palabra por ciclo en los accesos burst. El procesador superescalar de 1 GHz dispone de una
cache interna con líneas de 4 palabras.
En los programas, por término medio, un 30% de las instrucciones necesitan leer o escribir
un dato (una palabra) en memoria., la tasa de aciertos de la cache interna es del 90%, y, en un
20% de los accesos a datos se necesita reemplazar el bloque, en caso de que se haya
producido un fallo.
Solución
Para calcular el tiempo medio de acceso a memoria hay que tener en cuenta el tiempo de acceso a
cache, y a la memoria principal en el caso de fallo de cache. La expresión es:
Tacceso a1 t 1 1 a1 t 1 t M p reemplazo t línea
De todos los parámetros que forman la expresión anterior, del enunciado podemos deducir fácilmente
que a1 = 0.9, t1 = 1 ns, tM = 30 ns. La obtención de la probabilidad de reemplazo es algo más
compleja. Para calcularla hay que tener en cuenta que la cache L1 es unificada (contiene tanto datos
como instrucciones), por lo que no todos los fallos que se produzcan provocarán reemplazos. Sólo
provocarán reemplazos los fallos que se produzcan por accesos a datos, ya que las instrucciones no se
modifican durante la ejecución del programa. Por tanto, dado que habrá un reemplazo de línea en un
20% de los accesos a datos y que el 30% de las instrucciones acceden a datos, para un programa que
ejecute NI instrucciones tenemos:
N accesos_datos 0.3 NI
p reemplazo 0.2 0.2 0.046
N accesos_total NI 0.3 NI
Por último, sólo nos queda por calcular el tiempo de línea. Teniendo en cuenta que este parámetro
mide el tiempo de actualización de una línea en memoria principal cuando debe reemplazarse dicha
línea en memoria cache, como una línea está constituida por cuatro palabras, el tiempo de
actualización corresponde al del tiempo de un acceso burst a la memoria principal, es decir
6 + 1 + 1 + 1 = 9 ciclos de bus, y la frecuencia del bus es de 200 MHz, el tiempo de línea se obtiene
mediante la siguiente expresión:
N ciclos_burst 6 1 1 1
t línea 45 ns
Fbus 200 106
Para responder al segundo apartado, primero calcularemos el tiempo mínimo que tardaría el
procesador superescalar en ejecutar el programa, suponiendo un programa que ejecuta NI
instrucciones y que no hubiera retardos en la ejecución:
CPI NI CPI NI
TCPU CPI NI Tciclo CPI NI 109
F 1 109
Una vez calculado el tiempo de ejecución, se puede calcular el ancho de banda que debería
proporcionarle el sistema de memoria:
Para determinar el valor de CPI se iguala el ancho de banda del procesador y el ancho de banda que
proporciona la memoria, BMem:
1 1 109
BMem 9
palabras/s
Tacceso 4 .207 10 4 .207
1 1.3
4.207 CPI
de donde se puede despejar CPI = 5.47, o lo que es lo mismo, que el ancho de banda del sistema de
memoria limita la velocidad del procesador a 0.18 instrucciones por ciclo (en término medio). Como
puede verse, el ancho de banda que proporciona la memoria no sería suficiente ni siquiera para que el
procesador pueda funcionar como procesador segmentado (ejecutando una instrucción por ciclo).
En el caso de que la tasa de fallos de cache fuese del 95%, el tiempo de acceso medio a la memoria
sería (utilizando la misma expresión que en el apartado anterior):
109
BMem palabras/s
2.603
Como se puede observar, se ha producido una mejora de un factor de 1.61, es decir que un aumento
de un 5% en la tasa de aciertos ha ocasionado una mejora de aproximadamente un 60% en el ancho
de banda. El valor de CPI ha disminuido, pero sigue siendo mayor que 1:
por lo que aún no se puede procesar ni siquiera una instrucción por ciclo.
Si queremos aprovechar las capacidades superescalares del procesador no habría más remedio que
incrementar el ancho de banda de la memoria. Una estrategia para conseguir ese incremento en el
ancho de banda de la memoria es incrementar el número de palabras que se traen desde la memoria
cache interna en cada acceso a la misma (de hecho, esta es la alternativa usual en los procesadores
superescalares). Al estar la memoria cache integrada en el mismo chip que el procesador, no hay
problema por las posibles limitaciones en el número de terminales. Por ejemplo, si el procesador
superescalar fuera capaz de ejecutar hasta tres instrucciones por ciclo (CPI = 1 / 3), el ancho de banda
que demandaría la CPU sería:
1.3
BCPU 109 1.3 3 109 3.9 109 palabras/s
CPI
Para que el ancho de banda de la memoria fuera igual, se ´tendrían que leer/escribir W palabras en
cada acceso (asumiendo una tasa de aciertos del 95%):
109
BMem W palabras/s
2.603
de donde deducimos que sería necesario ampliar el ancho del bus de memoria a 11 palabras para que
el procesador pudiera ejecutar hasta 3 instrucciones por ciclo. Si este número es demasiado elevado
para la tecnología disponible, no habría más remedio que mejorar el tiempo medio de acceso a la
memoria (mayor tasa de aciertos, memorias más rápidas, etc.), o el rendimiento del procesador se vería
limitado por el acceso a memoria.
Se desea procesar una señal digital a, compuesta por muestras de 4 bytes, mediante el
siguiente algoritmo:
donde M = 3000 y N = 220. El código que permite implementar el cálculo de la función f está
constituido por 12 instrucciones de 32 bits, de las cuales 4 son de acceso a memoria para
leer/escribir datos, 4 son instrucciones de operaciones aritméticas, y 4 son de control del
bucle, actualización de punteros, etc. Si la señal está almacenada en memoria a partir de la
posición 0x00000000 y el código está alineado con una línea de cache:
Solución
En primer lugar evaluaremos el tiempo que consumiría el procesador si funcionase a pleno
rendimiento. Para ello, primero calcularemos el número de instrucciones que se ejecutan:
NI M N 3 N f 3000 2 20 3 12 36000 2 20 3
donde Nf es el número de instrucciones máquina que se han usado para codificar la función f.
Considerando que el procesador puede procesar 3 instrucciones por ciclo y que funciona a una
frecuencia de reloj de 1 GHz, se tiene que:
TCPU CPI NI Tciclo
NI
36000 2 20 3
12.58 s
IPC F 3 109
Ahora vamos a estimar el tiempo que requeriría el acceso a memoria, para ver si es menor que el del
procesador o no, ya que el mayor de los tiempos es el que nos permitirá estimar el tiempo mínimo de
procesamiento. Como en el acceso al código sólo se produce una primera falta, se puede despreciar en
el cómputo final de tiempo, ya que las debidas a los accesos a los datos son muchas más, como
veremos a continuación.
El volumen de datos es de 4 Mbytes (220 datos de 4 bytes cada uno), así que no se pueden alojar todos
en la cache, que sólo tiene un tamaño de 64 Kbytes. Por lo tanto, como no cabe la señal entera en la
cache, y dado que se va leyendo ordenadamente, se producirán tantos fallos como líneas ocupa la
señal, para cada acceso a la misma:
Tdatos 2 20
N fallos_datos M 3000 5 3000 217
Tlínea 2
El número de accesos a datos a los que da lugar el programa, teniendo en cuenta en el código de la
función f se realizan cuatro accesos a memoria, es:
ND M N 3 N accesos_f 3000 2 20 3 4 12000 2 20 3
Por lo tanto la tasa de fallos es de:
a D 1 f D 1 0.0313 0.9687
Como en cada iteración se modifican todos los valores del vector, habrá reemplazo cada vez que se
produzca un fallo de cache, excepto la primera vez que se capten las primeras 211 líneas de datos
(64 Kbytes) del vector, ya que la primera vez que se llene la cache con datos del vector no será
necesario realizar reemplazos. Por lo tanto, la probabilidad de que no haya reemplazo es de:
N líneas_sin_reemplazo 211
pno_reemplazo 5.2 106
N fallos_datos 3000 217
Por último, el tiempo de acceso a una línea mediante una ráfaga será de:
N ciclos_burst 8 1 1 1
t línea 80 ns
Fbus 100 106
Tacceso_datos a D t D 1 a D t D t M p reemplazo t línea
0.9687 1 1 0.9687 1 50 0.99 80 5.04 ns
Por lo que se puede concluir que el tiempo de ejecución del programa está limitado por el sistema de
memoria.
NI M N 3 N f 3000 2 20 3 9 27000 2 20 3
Al reducirse el número de instrucciones ejecutadas, también se reduciría el tiempo mínimo de
ejecución del código (suponiendo que la memoria pudiera proporcionarle todo el ancho de banda
necesario) en la CPU:
ND M N 3 N accesos_f 3000 2 20 3 1 3000 2 20 3
y el tiempo total de acceso a datos:
TMem ND Tacceso_datos 3000 2 20 3 5.04 109 15.86 s
Como vemos, sigue siendo la memoria la que más tarda, y la que mejor aproximaría el tiempo mínimo.
No obstante, como se puede comprobar, las diferencias entre los tiempos se han reducido bastante.
Realice una estimación lo más aproximada posible del tiempo mínimo que puede tardar en
ejecutarse el programa en este procesador.
NOTA: Puede considerar que, como no hay asignación en escritura en la cache, las escrituras se
harán directamente en memoria principal y que dicha escritura puede hacerse
concurrentemente a la ejecución del resto de instrucciones, incluyendo las de carga de
memoria dado que no hay instrucciones posteriores que utilicen los componentes
almacenados en memoria, también puede despreciar las faltas en la cache de instrucciones.
También puede asumir que la política de asignación de línea dentro de un conjunto es la que
proporciona mejores resultados respecto a las colisiones.
Solución
Una posible implementación del programa del enunciado podría ser la siguiente:
NI 11 n 11 2 20
Asumiendo que se van retirar tres instrucciones cada ciclo (sin atascos en el cauce), y que todos los
datos e instrucciones se pueden captar de las caches sin provocar faltas, el tiempo mínimo de
ejecución sería el siguiente:
NI 11 2 20
TCPU CPI NI Tciclo 1.92 103 s
IPC F 3 2 109
Sin embargo, como cada uno de los vectores ocupa un total de 4 × 220 = 222 bytes, y la cache sólo
tiene 64 KB = 216 bytes, se producirán bastantes fallos en el acceso a la cache de datos que no se
pueden despreciar. Todos estos fallos serán de tipo compulsivo (la cache está inicialmente vacía) o por
problemas de la capacidad de la cache, pero no se van a llegar a producir fallos por conflicto entre
diferentes accesos, ya que en el peor caso, en el que al leer los tres vectores se produzcan tres faltas
sobre líneas asociadas al mismo conjunto, no habrá conflicto, ya que cada conjunto tiene cuatro líneas.
Por tanto, teniendo en cuenta que cada línea de cache tiene un tamaño 32 bytes, y que el primer
elemento de cada vector está alineado al comienzo de una línea de cache, el total de fallos en el acceso
a la cache de datos será de:
Tdatos 3 2 22
N fallos_datos 3 217
Tlínea 25
El número de accesos a la cache de datos a los que da lugar el programa, teniendo en cuenta que se
leen tres vectores de n componentes, y que las escrituras se realizan directamente en la memoria
principal, es:
ND 3 n 3 2 20
N fallos_datos 3 217
fD 0.125
ND 3 2 20
y la de aciertos es de:
a D 1 f D 1 0.125 0.875
Como todas las escrituras se realizan directamente en memoria y concurrentemente con las lecturas,
no habrá que hacer reemplazos, por tanto, el tiempo medio de lectura de los datos será de:
Por tanto, el tiempo total necesario para leer todos los datos será:
Por lo que se puede concluir que el tiempo de acceso a datos limita el tiempo de ejecución del
programa.
a) ¿Cuál sería el código escalar para una arquitectura load/store que implemente
la secuencia de instrucciones vectoriales anterior?
NOTA: Considere la situación más favorable respecto a la ubicación de los vectores en memoria
principal y su correspondencia en cache.
Solución
Si se desea implementar el fragmento de código del enunciado en un procesador escalar, el código
podría ser así:
En el enunciado se afirma que el tiempo de ejecución de este fragmento de código está limitado por el
tiempo que se tarda en acceder a los datos. Este tiempo dependerá de la ubicación de los datos en la
memoria principal. Como se nos indica que consideremos la ubicación más favorable, asumiremos que
el primer dato de cada vector está alineado a una frontera de 32 bytes, de forma que se ocupen el
menor número de líneas, y que los vectores x e y están almacenados en posiciones de memoria tales
que la correspondencia directa de la memoria cache los llevará a líneas diferentes. De esta forma sólo
se producirán fallos de cache cada vez que se acceda a una línea diferente de la cache de datos.
Como el procesador es de 32 bits, asumimos que cada elemento del vector ocupa 4 bytes, por tanto, si
cada vector tiene n = 1024 elementos, ocupará 4 Kbytes de memoria. La ubicación que hemos
supuesto más arriba implica que sólo se cometerá un fallo de cache cuando se intente leer o escribir un
dato de una línea que no haya sido llevada a cache, con lo que el número de fallos de la cache de datos
en el acceso a un vector se puede calcular como:
Tvector 212
N fallos_vector 5 2 7 128
Tlínea 2
Además de para leer los elementos del vector y, como la cache realiza asignación en escritura (write-
allocate), cada vez que se intente escribir un elemento de x y no esté en cache se traerá una línea de
elementos de x. Por último, también hay que cargar las variables a y n, que supondremos que están las
dos en otra línea de cache, lo que añadirá un fallo más a los fallos que se produzcan en la
manipulación de los vectores. Por tanto, el total de fallos de la cache de datos es de:
Como se accede a los 1024 elementos de cada vector y a las variables a y n, el número total de accesos
a datos es:
ND 2 1024 2 2050
Por tanto, la tasa de fallos del programa en la cache de datos de este computador es de
N fallos_datos 257
fD 0.125
ND 2050
y la de aciertos es de:
a D 1 f D 1 0.125 0.875
Suponiendo que se tarda 1 ciclo de CPU en acceder a la cache, el tiempo medio de acceso a un dato
es:
Por lo que el tiempo total de acceso a datos, o lo que es lo mismo, el tiempo de ejecución del
programa en el procesador superescalar es de:
10. Se tiene una aplicación de procesamiento de la señal que debe realizar el cálculo del valor
medio de las muestras de una señal a compuesta por n muestras:
donde s está previamente inicializada a 0; cada una de las muestras ocupa 4 bytes y n = 219. El
código que permite implementar el cálculo está constituido fundamentalmente por un bucle
con 7 instrucciones de 32 bits: una de ellas es de acceso a memoria para leer datos, 3 son
instrucciones de operaciones aritméticas, y 3 son de control del bucle, actualización de
punteros, etc.
NOTA: Suponga que la señal está almacenada en memoria en posiciones consecutivas comenzando
en una posición alineada con una línea de cache.
Solución
El número de instrucciones que ejecuta el programa es de:
NI n N instrucciones_bucle 219 7
Teniendo en cuenta la descripción del procesador y del programa, en condiciones ideales, suponiendo
una arquitectura de memoria que pudiera suministrarnos tantos datos e instrucciones como el
procesador necesite, y que no se va a producir ningún tipo de riesgo que pueda atascar el cauce del
procesador, el tiempo mínimo de ejecución del programa sería:
NI 219 7
TCPU CPI NI Tciclo 1.835 103 s
IPC F 2 109
Ahora habrá que ver si la arquitectura de memoria es capaz de suministrarnos todos los datos e
instrucciones que necesitamos en ese tiempo. Para empezar, el programa consiste en 7 instrucciones,
es decir, 28 bytes de código que se pueden alojar en una línea de la cache de instrucciones al principio
de la ejecución. Una vez allí, cada instrucción se podrá captar en un ciclo de reloj, con lo que no
producirá ningún retardo. Por tanto, la penalización en la lectura de las instrucciones consistirá en una
falta en la cache de instrucciones, que se puede despreciar, ya que en los accesos a datos se producen
muchas más, como veremos más adelante.
El programa tiene que leer 219 datos de 4 bytes cada uno. Esto hace un total de 221 bytes de datos, que
no caben en una cache de datos de 64 Kbytes = 216 bytes. Así que si suponemos que el primer dato
está alineado con una línea de cache y la cache de datos está inicialmente vacía, se producirán tantos
fallos como líneas de datos:
Tvector 2 21
N fallos_datos 7 214
Tlínea 2
N fallos_datos 214
fD 19 0.03125
ND 2
a D 1 f D 1 0.03125 0.96875
Como no hay escrituras en memoria, no se producen reemplazos de líneas de cache, por tanto, el
tiempo medio para acceder a un dato en memoria sería:
Como el tiempo de acceso a datos es menor que el tiempo mínimo de procesamiento, el programa
tardaría en ejecutarse 1.835 ms, es decir, que el tiempo de ejecución estaría limitado por la velocidad
del procesador.
Si se desenrollara el bucle iteraría la mitad de veces, aunque cada iteración contendría más
instrucciones. Por tanto, el número de instrucciones que se ejecutarían sería:
n 219
NI 2 N instrucciones_bucle2 10 218 10
2 2
NI 2 218 10
TCPU2 CPI NI 2 Tciclo 1.31 103 s
IPC F 2 109
Esta mejora software ha conseguido que ahora el tiempo de ejecución del programa esté limitado por
el acceso a datos, es decir que la cota mínima para su tiempo de ejecución sea de 1.34 ms en vez de los
1.835 ms anteriores.
Otra posibilidad para mejorar el tiempo de mínimo de procesamiento es cambiar el procesador por
otro más rápido. Si se introdujera el código sin desenrollar en un procesador capaz de procesar una
instrucción más por ciclo, el tiempo mínimo de procesamiento sería:
NI 219 7
TCPU3 CPI 3 NI Tciclo 1.223 103 s
IPC3 F 3 109
De nuevo el tiempo de ejecución vuelve a estar limitado por el acceso a datos. Para concluir, podemos
destacar que si el tiempo de ejecución está limitado por el tiempo de procesamiento, siempre se puede
mejorar optimizando el código o cambiando el procesador por uno más rápido. Si por el contrario el
tiempo de ejecución está limitado por el acceso a datos, la mejora del tiempo de ejecución es más
complicada, porque habría que aumentar el ancho de banda con la memoria, y como cambiar el ancho
de los buses o su tiempo de ciclo está fuera de nuestro alcance, la única solución sería intentar que la
tasa de aciertos en cache sea mayor. Para ejemplos que procesen datos temporales, se puede reescribir
el código usando algunas técnicas de optimización como blocking, arrays de estructuras o estructuras de
arrays, pero para un programa como el que nos ocupa, con datos no temporales pero con un patrón de
accesos conocido y sencillo, la única solución sería usar instrucciones de precaptación con una
anticipación suficiente para que los datos estuvieran en cache cuando haya que accederlos, siempre
que el procesador las implemente este tipo de instrucciones.