Ya
en otro artículo hicimos referencia muy
por encima a la jerarquía de memoria. En este vamos a intentar explicar con un
poco más de detalle el concepto que hay detrás. Este tema resulta engañosamente
sencillo y se complica muy rápidamente en cuanto profundizamos un poco, así que
como estos artículos son más bien de tipo divulgativo intentaré no perderme en
tecnicismos y tecnología o conceptos puramente electrónicos.
Para
que un procesador pueda hacer un trabajo debe realizar infinidad de operaciones
sobre una serie de operandos, la memoria de todo el sistema existe
principalmente para proveer al procesador de operandos y almacenar los
resultados de las operaciones que efectúa sobre ellos. La situación ideal es
que el procesador pueda leer esos datos de forma instantánea pero los límites
de la tecnología imponen una velocidad máxima o un tiempo de acceso mínimo que
no es posible mejorar sin cambiar la tecnología.
En
vista de eso la primera solución que se nos ocurre es usar siempre el tipo de
memoria más rápido para construir nuestro sistema. Esto se choca de frente con
un problema: el coste. Aparte de otras consideraciones técnicas, el principal
problema es que cuanto más rápida es una memoria, más cara resulta y menos
densidad de información tiene. El siguiente gráfico extraído de Wikipedia lo
expresa muy claramente:
De
los apuntes de la asignatura podemos extraer una transparencia que pone de
relieve los saltos en velocidades que hay de un escalón de memoria al
siguiente:
Como
ya habíamos comentado antes, el orden de la memoria desde dentro del procesador
hacia fuera es (con una aproximación de los saltos):
Registros -> Velocidad x (1/4), Tamaño
x 8 -> Caché -> Vx(1/100) , Tx500
-> Principal -> Vx(1/1000) , Tx300
-> Virtual
La
idea básica detrás de la jerarquía de memoria es muy simple y a la vez muy
ambiciosa: organizar la memoria de forma que tengamos un sistema que funcione
casi a la velocidad de la más rápida de ellas pero con prácticamente el coste
de la más barata.
¿Cómo
hacemos esto? Eso es lo que veremos ahora con diversas técnicas pero la idea
más extendida es usar los periodos de menor actividad para traer los datos de
las memorias más lentas hacia las más rápidas con antelación a que el
procesador las necesite, de esta forma el procesador sólo tiene que esperar a
que los datos le lleguen de los registros en vez de tener que parar la
ejecución mientras espera que se extraigan datos de la memoria virtual.
Hoy
en día la memoria principal es tan barata y se instalan módulos tan grandes (no
es raro ver configuraciones de 4GB x 2 módulos x 3 canales = 24 GB) que a
simple vista uno podría creer que se podrían cargar todos los datos necesarios
para correr un programa en la memoria principal, sin tener que hacer un solo
acceso a disco duro durante su ejecución.
Cuando
el procesador necesita un dato primero lo busca en la memoria más cercana, si
lo encuentra se produce un acierto y el tiempo empleado es el tiempo necesario
para leer de ese tipo de memoria (latencia), que es siempre menor que el
correspondiente de escritura. Si no se encuentra se produce un fallo y entonces
se traerá del siguiente nivel de memoria un bloque de datos entre los cuales
deberá estar el solicitado. El tiempo empleado en esta operación es bastante
superior al del caso de acertar, como puede deducirse de las figuras que ya
hemos visto. A este tiempo empleado le llamamos penalización por fallo.
El
tiempo medio de acceso se puede calcular entonces de acuerdo a la siguiente
fórmula (de los apuntes de AGM):
En
base a la forma de operar de los procesadores y programas actuales, se pueden
postular dos principios que después servirán para diseñar optimizaciones a la
jerarquía: el principio de localidad temporal que nos dice que habitualmente
los datos usados volverán a usarse en un corto espacio de tiempo, y el de
localidad espacial que nos dice que normalmente usaremos datos adyacentes a uno
que ya hayamos extraído.
Conviene
antes mencionar que en toda jerarquía se deben respetar las propiedades de
inclusión (que se consigue replicando todos los datos de la memoria más pequeña
en el resto de memorias de la jerarquía) y coherencia (que todas las copias de
la misma información sean iguales).
Vamos
a plantar las optimizaciones dividiendo los enfoques en:
-
Política de emplazamiento:
·
Directa
·
Asociativa
·
Por conjuntos
-
Política de reemplazamiento
·
Aleatoria
·
LRU (Least Recently Used)
·
FIFO
(First In First Out)
-
Política de escritura
·
Escritura directa
·
Post-escritura
·
Con asignación en escritura
·
Sin asignación en escritura
Política de emplazamiento: la memoria caché
se divide en marcos que pueden alojar bloques de información que provengan de la
memoria principal. En la memoria directa le corresponde un único marco donde
puede alojarse, en la asociativa puede alojarse en cualquier marco y en la de
por conjuntos los marcos se asocian por conjuntos de manera que cada bloque de
datos puede alojarse en cualquiera de los marcos que hay en el conjunto que le
corresponde.
Política de reemplazamiento: gestiona
las actuaciones en caso de fallo; debemos introducir nuevos datos en la memoria
pero ¿qué datos eliminamos para hacer sitio? Podemos hacerlo aleatoriamente,
eliminando los datos que más tiempo llevan sin usarse o eliminando los que más
tiempo llevan en memoria. Vuelvan a leer esa última frase; parece lo mismo pero
no lo es.
Política de escritura: las lecturas de
memoria son más frecuentes que las escrituras, pero estas últimas son más
lentas con lo que conviene gestionarlas adecuadamente. En el modo de escritura
directa cada vez que se modifica un bloque se escribe en la memoria caché y en
la principal, en el modo post-escritura sólo se modifica en la caché y se espera a que el bloque sea reemplazado
para modificar la memoria principal, además se tiene un bit que indica si se ha
modificado ese dato o no en la caché de manera que se actualice en memoria
principal sólo cuando cambie.
Para
evitar penalizaciones por fallos en la escritura estos pueden ignorarse o no
dependiendo del criterio: en el modo con asignación de escritura se tratan
igual que los fallos de lectura (es decir; se resuelven) y en el modo “sin” el
bloque se modifica directamente en la memoria principal y no se trae a la
caché.
No hay comentarios:
Publicar un comentario