Está en la página 1de 24

Capítulo 4

Interconexión procesador – memoria

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

donde Tinstrucción es la longitud de las instrucciones en bytes, IPC el número de instrucciones


que el procesador puede terminar por ciclo, y F la frecuencia del procesador. Además, si el
acceso a 4 instrucciones implica acceder a aproximadamente 2 datos (para lectura/escritura),
el ancho de banda necesario debe incrementarse en:

Bdatos  ND  Tdato  F  2  4  109 bytes/s  8 Gbytes/s (2)

asumiendo que la longitud de cada dato es Tdato = 4 bytes. Es decir que se necesitaría un
ancho de banda de unos

BCPU  Binstrucciones  Bdatos  16  8  24 Gbytes/s (3)

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.

La Figura 1 permite resumir los distintos tipos de correspondencia entre direcciones de


memoria principal y cache para los distintos tipos de cache. Con marca, conjunto, y vía se hace
referencia a los distintos campos que se pueden distinguir en las direcciones de memoria
principal y memoria cache. El número de bits de los campos se indicará con Nmarca, Nconjunto y
Nvía, respectivamente. En el caso de una memoria cache asociativa por conjuntos, el campo
conjunto indica en qué conjunto de la cache se puede almacenar la línea y el campo vía en qué
línea del conjunto se ha almacenado (en función del uso de las líneas del conjunto en cada
momento). Por tanto, la cache está organizada en 2Nconjunto conjuntos de 2Nvía líneas, es decir,
que su tamaño es de 2Nconjunto + Nvía. En uno de los extremos de esta organización está la cache
con correspondencia directa, en la que cada conjunto está compuesto por una sola línea. En este
caso, Nvía = 0 y el campo conjunto indica la línea en cache a la que irá la línea de memoria
principal, por lo tanto el número de líneas de la cache es 2 Nconjunto. En el otro extremo está la
cache completamente asociativa, organizada como un solo conjunto, por lo que Nconjunto = 0 y
Nvía = 0. En este caso se establece una correspondencia asociativa entre el campo marca, que
está formado por todos los bits de la dirección de memoria principal (excepto los bits que
indican la posición del byte en la línea) y la línea de cache en la que finalmente se almacena la
línea de memoria principal.

Marca Conjunto Desp. Dirección de memoria principal

Conjunto Vía Desp. Dirección de cache

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)

donde a1 y a2 son, respectivamente, la tasa de aciertos a las caches L1 y L2 y t1 y t2 los


tiempos de acceso a las caches de cada nivel. Para que sea beneficioso utilizar una jerarquía
de dos niveles de cache frente a una de un solo nivel debe suceder que T3 < T2 y por lo
tanto, considerando las expresiones anteriores, debe verificarse que:

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

Figura 2. Acceso a una matriz de celdas de memoria DRAM.

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

Figura 3. Mejora del ancho de banda de las memorias DRAM.

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:

N bits  N accesos_línea  Fbus


Bburst  (9)
N ciclos  N accesos_línea  1

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:

T  N accesos  Tacceso  8  50  400 ns

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

2. Un computador dispone de un procesador de 64 bits y utiliza una memoria cache con


correspondencia directa de 64 Kbytes y una memoria principal de 32 Mbytes.

a) Teniendo en cuenta que cada línea de cache es de 128 bytes, indique la


posición de cache que ocupa la instrucción que se encuentra en la dirección
de memoria 0x0110ab48.
b) ¿Y si, con los mismos datos, la memoria cache fuese asociativa por
conjuntos de 4 vías?

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):

tamaño de la cache 216


N líneas_cache   7  29
tamaño de cada línea 2

por lo que se necesitan 9 bits para designar una línea.

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.

Marca Línea Desp. Dirección de memoria

Línea Desp. Dirección de cache

9 bits 9 bits 7 bits

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.

REPRESENTACIÓN DIRECCIÓN RAM MARCA LÍNEA DESP.

BINARIO 1 0001 0000 1010 1011 0100 1000 1 0001 0000 1 0101 0110 100 1000
HEXADECIMAL 0x0110ab48 0x110 0xab48

Tabla 1. Marca, número de línea y desplazamiento de la dirección de memoria del problema 2 en la


cache con correspondencia directa.

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

Marca Conjunto Desp. Dirección de memoria

Conjunto Vía Desp. Dirección de cache

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.

REPRESENTACIÓN DIRECCIÓN RAM MARCA CONJUNTO VÍA DESP.

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.

3. Se dispone de un computador basado en el Pentium II (caches internas asociativas por


conjuntos de 2 vías, de 16 Kbytes para datos, y de 16 Kbytes para instrucciones) que dispone
de una memoria cache de segundo nivel de 256 Kbytes asociativa por conjuntos de 4 vías, y
una memoria principal de 64 Mbytes. Si las líneas de cache son de 32 bytes, indique las
posiciones que ocupa en las memorias caches una instrucción de programa que se encuentra
en la posición de memoria 0x0012aac4.

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:

tamaño de la cache L1 214


N líneas_cache_L1   5  2 9  512
tamaño de cada línea 2

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

Marca Conjunto Desp. Dirección de memoria

Conjunto V Desp. Dirección de cache


ía
8 bits 5 bits
1 bit

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.

REPRESENTACIÓN DIRECCIÓN RAM MARCA CONJUNTO VÍA DESP.

BINARIO 00 0001 0010 1010 1010 1100 0100 0 0000 1001 0101 0101 0110 X 0 0100
HEXADECIMAL 0x0012aac4 0x0095 0x15?4

Tabla 3. Marca, conjunto y desplazamiento dentro de la línea de la cache L1 de la dirección de memoria


del problema 3.

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.

10 bits 11 bits 5 bits

Marca Conjunto Desp. Dirección de memoria

Conjunto Vía Desp. Dirección de cache

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

Tabla 4. Marca, conjunto y desplazamiento dentro de la línea de la cache L2 de la dirección de memoria


del problema 3.

4. En un computador, la cache interna del procesador tiene un tiempo de acceso de 2.5 ns y


una tasa de aciertos del 80%, y la memoria principal tiene un tiempo de acceso de 10 ns.
Suponga que dispone de una memoria cache SRAM con un tiempo de acceso de 5 ns.

a) ¿Qué tasa de fallos ha de tener para que el tiempo de acceso medio a


memoria se reduzca con respecto a la situación anterior?

b) ¿Es posible obtener un tiempo de acceso medio de 3.7 ns?

c) ¿Cuál es el mínimo tiempo de acceso medio que se puede conseguir?

NOTA: Considere que las memorias cache son look-through.

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:

T2  a 1  t 1  1  a 1  t 1  t M   0.8  2.5  1  0.8   2.5  10  4.5 ns

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

De esta forma, para que T3 < T2 es preciso que

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

de donde se puede despejar a2 = 0.9.

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.

5. Un microprocesador incluye una CPU de 32 bits y caches separadas para datos e


instrucciones, de 4 Kbytes cada una, con líneas de 32 bytes, y organizadas como caches
asociativas por conjuntos de dos vías. El tiempo de acceso a la memoria cache es de 5 ns, la
política de reemplazo es LRU (se reemplaza la línea que lleve más tiempo sin utilizarse), y la
de actualización es de post-escritura (write-back). El microprocesador está conectado a dos
memorias externas separadas para datos e instrucciones mediante dos buses independientes
de 100 MHz y 64 líneas de datos cada uno, con un tiempo de acceso de 50 ns y accesos burst
5-1-1-1.

Se está ejecutando un programa en el que la secuencia de instrucciones que se encuentran


entre las direcciones 0x0000 – 0x1fff constituyen un bucle que se repite 30 veces, y tras ellas
existe una secuencia de instrucciones que ocupa desde la dirección 0x2000 hasta la 0x2ff8, y
que se ejecuta una vez. En el 60% de las instrucciones del programa, se hace referencia a un
dato, ocasionándose un total de 500 fallos en el acceso a estos datos.

a) Suponiendo que las caches están inicialmente vacías ¿Cuál es la tasa de


aciertos para el acceso a los datos y a las instrucciones en el programa?

b) ¿Cuál es el tiempo medio de acceso a la memoria de instrucciones?

c) ¿Cuál es el tiempo medio de acceso a la memoria de datos si el 20% de los


fallos por acceso a datos dan lugar a reemplazo de la línea con actualización
de la línea reemplazada en memoria?

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.

N fallos_iteración  128  128  256

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_bucle  N fallos_iteración  N iteraciones  256 30  7680


La secuencia de instrucciones que vienen a continuación ocupa menos de 4 Kbytes, por lo que se
podrán introducir en la cache. Como los bits 5 a 10 de la posición 0x2000 son cero, las líneas de la
secuencia se empiezan a cargar desde el conjunto 0, así que se producirán tantos fallos como líneas
haya en la secuencia. El número de líneas de la secuencia se obtiene desplazando las direcciones de
comienzo y final cinco bits a la derecha (las líneas son de 32 bytes) y restando los datos resultantes. El
resultado que queda es 1111111 = 0x7f, por lo tanto, desde la línea 0x00 a la 0x7f hay 128 líneas. Es
decir, se producen 128 fallos en la secuencia final del programa:

N fallos_final  128

El número total de fallos ocasionados por el programa es:

Nfallos_programa  Nfallos_bucle  Nfallos_final  7680  128  7808

Para calcular la tasa de fallos también es necesario conocer el número de instrucciones que se han
ejecutado:

N iteraciones  Tbucle  Tfinal 30  8192  4088


NI    62462
Tinstrucción 4

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

En cuanto a la tasa de fallos de la memoria cache de datos, como el número de instrucciones


ejecutadas es 62462, y como se generan accesos a datos en un 60% de las instrucciones, el número de
accesos a la memoria cache de datos es:

ND  NI 0.6  62462 0.6  37477

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

El tiempo medio de acceso a la memoria cache de instrucciones es

TI  a I 1  t I 1  1  a I 1  t I 1  t MI   0.875 5  1  0.875  5  50  11.25 ns

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.

a) ¿Cuál es el tiempo medio de acceso a la memoria?

b) ¿Cuántas instrucciones por ciclo se pueden ejecutar, por término medio, en


el procesador? ¿y si la tasa de aciertos pasa a ser de un 95%?

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

Sustituyendo los correspondientes valores, se tiene que

Tacceso  0.9  1  1  0.9  1  30  0.046  45  4.207 ns

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  109
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:

N accesos_total NI  0.3  NI 1.3


BCPU     109 palabras/s
TCPU CPI  NI  109 CPI

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

lo que nos deja la siguiente expresión:

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):

Tacceso  0.95  1  1  0.95  1  30  0.046 45  2.603 ns

y el ancho de banda proporcionado por el sistema de memoria:

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:

CPI  2.603  1.3  3.38

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

Igualando el ancho de banda demandado por el procesador y el proporcionado por el sistema de


memoria podemos calcular W:

W  3.9  2.603  10.1

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.

7. Suponga un computador que dispone de un procesador superescalar que funciona a 1 GHz.


y que puede terminar hasta 3 instrucciones por ciclo, con una memoria cache interna para
datos y otra para instrucciones de 64 KBytes cada una, mapeo directo, líneas de 32 bytes, y
tiempo de acceso de un ciclo de reloj. La memoria cache de datos utiliza política de
actualización de post-escritura (write-back). La memoria principal de 256 MBytes tiene un
tiempo de acceso de 50 ns y se conecta a través de un bus de 64 bits a 100 MHz. que utiliza
accesos burst 5-1-1-1 para transferir las líneas de cache.

Se desea procesar una señal digital a, compuesta por muestras de 4 bytes, mediante el
siguiente algoritmo:

for (k = 0 ; k < M ; k++)


for (i = 3 ; i < N ; i++)
a(i) = f(a(i), a(i – 1), a(i – 2), a(i – 3))

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:

a) Realice una estimación del tiempo mínimo que tardaría en ejecutarse la


aplicación.

b) Si se utiliza un compilador que es capaz de reducir el número de


instrucciones de acceso a memoria, (pasando de 4 a 1 instrucciones de
acceso a memoria por muestra) ¿Cuál sería ahora el tiempo mínimo
estimado?

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:

N fallos_datos 3000 217 217


fD     0.0313
ND  
12000 2 20  3 4  2 20  3  
y la de aciertos 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  106
N fallos_datos 3000 217

Así que la probabilidad de reemplazo será:

p reemplazo  1  pno_reemplazo  1  5.2  106  0.99

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

Esto significa que el tiempo medio de acceso a memoria es:


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

y el tiempo para acceder a todos los datos será:


 
TMem  ND  Tacceso_datos  12000 2 20  3  5.04  10 9  63.42 s

Por lo que se puede concluir que el tiempo de ejecución del programa está limitado por el sistema de
memoria.

Si aplicamos el compilador para optimizar el código y reducir el número de accesos a memoria de 4 a


1 en la implementación de la función f, el número de instrucciones que se ejecutarían sería:

 
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:

TCPU  CPI  NI  Tciclo 


NI

27000 2 20  3  
 9.44 s
IPC  F 3  109

El número de accesos a datos pasaría a ser:

 
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  109  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.

8. Suponga que el siguiente programa

for (i = 0 ; i < n ; i++)


d[i] = a[i] + b[i] + c[i];

se ejecuta en un procesador superescalar de 32 bits a 2 GHz que puede terminar un máximo


de 3 instrucciones por ciclo, con una memoria cache interna para datos y otra para
instrucciones, de 64 Kbytes cada una, líneas de 32 bytes, mapeo asociativo por conjuntos de
4 vías, política de actualización de post-escritura (write-back), sin asignación de cache en
escritura (no write-allocate) y tiempo de acceso de un ciclo de reloj de CPU. La memoria
principal, de 512 Mbytes, tiene un tiempo de acceso de 50 ns y se conecta a través de un bus
de 64 bits a 200 MHz que utiliza ciclos burst 10-2-2-2 para transferir las líneas de cache. Las
matrices que se suman tienen 220 elementos y el primer elemento de a está en la dirección
0x0800000, el primero de b en la dirección 0x1000000, el primero de c en la dirección
0x1800000, y el primero de d en la dirección 0x2000000.

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:

add r1, r0, r0 ; r1 = 0 (índice para recorrer los vectores)


lw r2, n ; r2 = n (número de elementos que quedan por procesar)
inicio: lw r3, a(r1) ; r3 = a[i]
lw r4, b(r1) ; r4 = b[i]
lw r5, c(r1) ; r5 = c[i]
add r6, r3, r4 ; r6 = a[i] + b[i]
add r6, r5, r6 ; r6 = r6 + c[i]
sw d(r1), r6 ; d[i] = r6
subi r2, r2, #1 ;n=n–1
addi r1, r1, #4 ; desplazamiento para el siguiente elemento
bnez r2, inicio ; saltamos si n != 0

El número de instrucciones que se ejecutan es:

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  103 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

Por lo que la tasa de fallos de la cache de datos es de:

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:

Tacceso_datos  a D  t D  1  a D  t D  t M   0.875 0.5  1  0.875  0.5  50  6.75 ns

Por tanto, el tiempo total necesario para leer todos los datos será:

TMem  ND  Tacceso_datos  3  2 20  6.75  10 9  21 10 3 s

Por lo que se puede concluir que el tiempo de acceso a datos limita el tiempo de ejecución del
programa.

9. Considere el siguiente bucle, en el que x e y son dos vectores de números almacenados en


coma flotante:

for (i = 0 ; i < n ; i++)


x[i] = a * y[i];

Este programa se ejecuta en un procesador superescalar de 32 bits a 1 GHz que puede


terminar un promedio de instrucciones por ciclo tal que el tiempo de ejecución está limitado
por el tiempo de acceso a los datos. El computador tiene una memoria cache interna para
datos y otra para instrucciones de 64 Kbytes cada una, líneas de 32 bytes, mapeo directo,
política de actualización de post-escritura (write-back), con asignación de cache en escritura
(write-allocate), y tiempo de acceso de un ciclo de reloj de CPU. La memoria principal de
512 Mbytes tiene un tiempo de acceso de 50 ns y se conecta a través de un bus de 64 bits a
100 MHz que utiliza ciclos burst 5-1-1-1 para transferir las líneas de cache.

a) ¿Cuál sería el código escalar para una arquitectura load/store que implemente
la secuencia de instrucciones vectoriales anterior?

b) Si el vector que se multiplica por el escalar tiene n = 1024 elementos,


¿cuánto tarda en ejecutarse el programa?

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í:

lf f0, a ; Cargamos la constante a


add r1, r0, r0 ; Inicializamos el índice i = 0
lw r2, n ; Tamaño del vector en elementos
slli r2, r2, #2 ; Multiplicamos por 4 para obtener el tamaño del vector en
; bytes. Cada elemento ocupa 32 bits (4 bytes)
bucle: lf f2, y(r1) ; Cargamos x[i]
multf f4, f0, f2 ; Multiplicamos y[i] por a
sf x(r1), f4 ; Guardamos el resultado
addi r1, r1, #4 ; Pasamos al siguiente elemento
sub r3, r1, r2 ; Comprobamos si hemos acabado
bnez r3, bucle ; Saltamos si quedan elementos

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:

N fallos_datos  2  N fallos_vector  1  2  128  1  257

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:

Tacceso_datos  a D  t D  1  a D  t D  t M   0.875 1  1  0.875  1  50  7.25 ns

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:

TMem  ND  Tacceso_datos  2050 7.25  109  0.015  103 s

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:

for (i = 0 ; i < n ; i++)


s = (s * (i – 1) + a[i]) / i;

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.

Suponga un computador que dispone de un procesador superescalar que funciona a 1 GHz.


y que puede terminar hasta 2 instrucciones por ciclo, con una memoria cache interna para
datos y otra para instrucciones de 64 Kbytes cada una, líneas de 128 bytes, mapeo directo,
política de actualización de post-escritura (write-back), y tiempo de acceso de un ciclo de reloj
de CPU. La memoria principal, de 512 Mbytes, tiene un tiempo de acceso de 50 ns y se
conecta a través de un bus de 64 bits a 100 MHz. que utiliza ciclos burst 5-1-1-1-1-1-1-1-1-1-
1-1-1-1-1-1 para transferir las líneas de cache.

a) Realice una estimación del tiempo mínimo que tardaría en ejecutarse la


aplicación.

b) Realice la misma estimación si modifica el código para aplicar un


desenrollado de orden 2 sobre el bucle anterior. En este caso, en cada
iteración tiene que calcular s = (s * (i – 1) + a[i] + a[i + 1]) / (i + 1), el
número de instrucciones de acceso a memoria es 2, el de operaciones
aritméticas es 4, y el de actualizaciones de punteros y control del bucle es 4.

c) Repita el primer apartado suponiendo que el procesador es capaz de


terminar 3 instrucciones por ciclo.

d) Compare las estimaciones de los tres apartados anteriores y extraiga


conclusiones.

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 103 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

Por tanto, la tasa de fallos de la cache de datos será:

N fallos_datos 214
fD   19  0.03125
ND 2

y la de aciertos será de:

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:

Tacceso_datos  a D  t D  1  a D   t D  t M   0.96875 1  1  0.96875  1  50  10 9  2.5625 10 9

Como hay que leer 219 datos, el tiempo total será:

TMem  ND  Tacceso_datos  219  2.5625 10 9  1.343 10 3 s

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

y el tiempo mínimo que tardaría en ejecutarse en el procesador sería:

NI 2 218  10
TCPU2  CPI  NI 2  Tciclo    1.31 103 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 103 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.

También podría gustarte