Logo Studenta

La cache, base de la jerarquia de memoria

¡Este material tiene más páginas!

Vista previa del material en texto

La cache, base de la jerarquía de memoria
Manuel Alejandro Barranco González
Iván Guardia Hernández
1.- Introducción
Una de las partes fundamentales de una computadora es su sistema de
memoria. El rendimiento de ésta condicionará las prestaciones del resto del
sistema. Hoy en día podemos escoger entre muchos tipos de memoria
distintos. Podemos elegir entre aquellas que son rápidas y pequeñas y las que
son lentas pero con gran capacidad de almacenamiento. Interesa tener un
sistema de memoria de tamaño grande y que permita acceder a la información
de forma rápida. Todo esto nos conduce a organizar diferentes tipos de
memoria de forma que podamos obtener un mayor rendimiento.
 ¿Cómo? Los datos que se requieren durante la ejecución de un
programa suelen estar ubicados en una misma región de memoria y, además,
suelen ser accedidos muchas veces en un intervalo de tiempo corto. Estos
resultados se conocen con los nombres de principio de localidad espacial y
principio de localidad temporal.
Siguiendo estos principios, la memoria se construye jerárquicamente,
de forma que las partes más rápidas se encuentran más cerca del procesador.
A medida que bajamos de nivel en la jerarquía, perdemos velocidad pero
ganamos capacidad de almacenamiento. De esta forma, se consigue que todo
el sistema de memoria tenga un rendimiento próximo al de su parte más
rápida. Básicamente, podemos dividir la jerarquía en tres niveles. El primero
de todos, y sobre el que se apoya el procesador, está formado por la memoria
cache. Ésta posee una velocidad elevada, pero tiene poca capacidad. El
siguiente nivel está formado por la memoria principal, mucho más grande,
pero de menor velocidad. El último lo constituye la memoria secundaria, de
una capacidad enorme pero muy lenta. Este último nivel se gestiona y
relaciona con el resto de la jerarquía para conseguir de forma virtual una
memoria de gran capacidad y de velocidad próxima a la cache.
¿Qué es lo que se debe tener en cuenta en la jerarquía para que todo el
sistema de memoria tenga un rendimiento próximo al de la cache? La
información de cada nivel está organizada en forma de bloques. Cuando se
accede a un nivel de memoria para obtener un dato, éste puede tenerlo o no. Si
lo tiene ha habido un acierto, y si no lo tiene ha habido un fallo, en cuyo caso
hay que ir al nivel inferior para recuperar el bloque que contiene el dato. La
frecuencia de aciertos es la fracción de accesos a memoria encontrados en el
nivel superior, mientras que la frecuencia de fallos es la fracción de accesos
no encontrados. Ambos se suelen representar con un porcentaje. El tiempo de
acierto esta formado por el necesario para determinar que a habido un acierto,
más el tiempo en acceder al dato en el nivel superior. La penalización de fallo
es el tiempo necesario para traer un bloque del nivel inferior al superior,
dividiéndose en tiempo de acceso y transferencia. Por tanto, los niveles
inferiores a la cache deben ser diseñados de manera que permitan reducir la
penalización de fallo y el tiempo de acierto de ésta.
En primer lugar describiremos la estructura básica de la cache con el
fin de profundizar más en su funcionamiento, así como los distintos aspectos
que hay que tener en cuenta para obtener un buen rendimiento de la misma.
Posteriormente analizaremos la organización de la memoria principal y las
técnicas de memoria virtual que se pueden utilizar para conseguir nuestro
objetivo, aproximar el rendimiento del sistema de memoria al de la cache.
2.- Memoria cache
La cache es el primer nivel dentro de la jerarquía de memoria,
situándose bajo el procesador. Hoy en día la utilización de esta memoria se
ha hecho casi indispensable para cualquier computadora, pudiendo utilizar en
muchos de ellos varias memoria cache.
Después de los registros del procesador, es el tipo de memoria más
rápido, pero a su vez es el que dispone de menos espacio donde poder guardar
la información. Por ello, para conseguir aumentar el rendimiento del sistema
utilizando la cache se coloca la información que más se utilizará en ella.
Como a este nivel los accesos a memoria son más rápidos, la información
podrá ser obtenida antes.
Al disponer de menos memoria cache que la que hay en niveles
inferiores de la jerarquía, mucha información tendrá que ser buscada fuera de
ella. El bloque de información que contiene el dato que provocó el fallo debe
ser cargado en la cache sobre otro bloque ya situado en ella. El bloque
seleccionado para ser reemplazado debe ser escrito en el siguiente nivel,
siguiendo una estrategia determinada. Como el acceso a un nivel inferior
supone una pérdida de tiempo o penalización de fallo, es muy importante
tener en cuenta cual es la frecuencia de acierto, es decir, la probabilidad de
encontrar la información en la cache. Para aumentar esta tasa de acierto se
debe encontrar la forma de ubicar y reemplazar la información en la cache
que aproveche lo mejor posible el principio de localidad espacial.
Pero no solamente es importante construir la cache de forma que se
incremente la frecuencia de acierto. También es necesario conseguir que el
tiempo de acierto y la penalización de fallo sean lo más pequeños posible.
Desde el punto de vista de la cache se pueden conseguir estos objetivos
mediante una organización que permita determinar rápidamente si un bloque
de información se encuentra en la cache o no, y mediante una política de
escritura de los bloques en los niveles inferiores a ella que sea acertada.
2.1.- Como podemos organizar la memoria cache
Una dirección de memoria generada por un programa esta compuesta
por un número de página, seguido de un desplazamiento; con el numero de
página obtenemos el marco de memoria correspondiente a través de una tabla
de páginas, para luego sumarle el desplazamiento y así obtener la dirección
física. En la cache, la dirección de memoria física la dividimos en tres partes:
una etiqueta para identificar los bloques, un índice que ayudará a localizar y
colocar los bloques, y por ultimo un desplazamiento de bloque que se usa para
seleccionar el dato deseado del bloque. Internamente cada bloque que se
encuentra dentro de la cache esta compuesto por los datos, la etiqueta que lo
identifica, y además un bit de validez que nos sirva para saber si los datos que
hay se encuentran son válidos. Como veremos más adelante, hay otro bit de
modificación que permite saber si los datos han sido cambiados mientras
estaban en la cache.
Básicamente podemos organizar la memoria cache de tres formas
diferentes: correspondencia directa, asociativa por vías y completamente
asociativa.
1.- Correspondencia directa: Cada bloque tiene un solo lugar donde
puede estar dentro de la memoria. La búsqueda del bloque se basa en localizar
el bloque dentro de una única tabla a través del índice de la memoria física,
luego se compara la etiqueta saliente de la cache con la etiqueta de la
memoria física, y si coinciden ya tenemos los datos solicitados.
A continuación mostramos un esquema que ilustra este tipo de
organización.
2.- Asociativa por vías: Cada bloque se puede colocar en un conjunto
restringido de lugares de la cache. Un conjunto es un grupo de dos o más
bloques dentro de la cache. En concreto si hay N bloques organizados en la
cache se dice que es una cache asociativa de N vías. A continuación se
muestra un ejemplo con 2 vías, donde se busca en paralelo la etiqueta en
ambos bloques. Luego, con un pequeño circuito se determina en que bloque
se acertó, y así se escogen los datos correctos a través de un multiplexor:
Seguidamente mostramos un esquema para clarificar este tipo de
organización.
 etiqueta índice desplazamiento
etiqueta datos
 =?
 acierto/fallo CPU
3.- Completamente asociativa: Cada bloque se puede colocar en
cualquier lugar de la cache. En concreto, podemos decir que es un caso de
memoria asociativa con el máximo numero de vías posibles. Es decir, existe
un solo conjunto que posee todos los bloques. Por tanto no existeel campo de
índice y sólo usamos la etiqueta. Para encontrar el bloque buscado se
comparará en paralelo la etiqueta de la dirección física con todas las etiquetas
de cada conjunto.
 etiqueta índice desplazamiento
etiqueta datos
 =?
CPU
 =?
2.2.- Política de reemplazado en un fallo de cache
Cuando se intenta acceder a un bloque que no se encuentra en la cache
hay que ir a buscarlo al nivel inmediatamente inferior, y posteriormente
colocarlo en la cache para los futuros accesos. En este momento hay que
decidir que bloque quitamos para colocar el nuevo. Es muy importante la
política de reemplazo, ya que de ella dependerá la tasa de aciertos.
Si usamos una correspondencia directa no hay elección, cada bloque
solo puede ser ubicado en un sitio. De esta manera se simplifica mucho el
hardware adicional que tenemos que poner.
Si de los bloques candidatos hay alguno que tiene el bit de no válido,
entonces este ya será la víctima. El problema viene cuando todos son válidos,
entonces tenemos que adoptar alguna política de reemplazo, en concreto hay
tres maneras de escoger la víctima:
1.- Aleatorio: Simplemente escogemos un bloque de forma
pseudoaleatoria. De esta manera se simplifica el hardware que tenemos que
añadir.
2.- Menos recientemente utilizado: Se basa en la localidad temporal de los
datos, donde cogemos el que hace más que no se usa. Se requiere un control
del tiempo de acceso de cada bloque, esto puede ser muy costoso en hardware
y tiempo de selección, pero a cambio no ofrece mejor tasa de acierto.
3.- El primero en llegar es el primero en salir: Los bloques sustituidos van
saliendo en el mismo orden en el que llegaron.
2.3.- Escrituras en la cache
En los casos de lectura de bloques se podía realizar en paralelo la
verificación de la etiqueta y la lectura del dato, así si había acierto ya
teníamos el dato y no había tiempo perdido por culpa de la comprobación de
la etiqueta. En cambio, en una escritura esto no puede ser así porque sólo se
podrá hacer si realmente es el bloque correcto, y no se sabrá hasta pasada la
verificación de la etiqueta. Por este motivo, las escrituras suelen ser más
lentas que las lecturas. Podemos adoptar dos estrategias para escribir en la
cache:
1.- Escritura directa: La información se escribe tanto en la cache como en
la memoria principal.
2.- Postescritura: La información se escribe sólo en la cache, y si
posteriormente se tiene que reemplazar el bloque, entonces este se escribe en
niveles inferiores de la jerarquía antes de ser sustituido. Para llevar a cabo esta
estrategia hay que añadir un bit de modificado a cada bloque dentro de la
cache, que será el indicativo para saber si tenemos que actualizar el bloque en
el nivel inferior a la hora de sustituirlo por otro en la cache.
2.4 Variantes sobre el modelo original
Una primera variante consiste en subdividir la cache en dos, una para
los datos y otra para las instrucciones. Si se dispone de puertos de memoria
separados para ambos, podemos trabajar en paralelo, casi doblando así el
ancho de banda entre la cache y la CPU. Además, de esta manera se puede
aumentar el rendimiento de ambas por separado, teniendo en cuenta el uso
individual que se hará de cada una de ellas.
Otra variante se basa en que cada bloque de la cache puede almacenar
más datos. Si aumentamos el tamaño del bloque, se aprovecha más la
localidad espacial. Pero no hay que descuidar que el incremento del tamaño
del bloque supone reducir el número de bloques y por tanto desaprovechar la
localidad temporal. Lo ideal sería buscar el tamaño idóneo para maximizar el
uso de la localidad temporal y espacial.
3.- Relación entre la memoria principal y la cache
La memoria principal se encarga de dar servicio a la cache y a las
operaciones de entrada y salida. Es el siguiente nivel después de la cache y
por tanto condiciona el rendimiento de ésta. Recordemos que en una jerarquía
los niveles superiores se apoyan en los inferiores, de forma que la
penalización de fallo en el nivel superior depende del tiempo de acceso del
nivel inferior y el tiempo de transferencia entre ambos niveles. Así pues, es
importante encontrar una organización adecuada con el fin de reducir ambos
tiempos y conseguir una penalización de fallos pequeña para la cache.
Normalmente es mucho más fácil reducir el tiempo de transferencia
entre los dos niveles de memoria que decrementar el tiempo de acceso. Esto
es debido a que es más sencillo aumentar el ancho de banda de la memoria,
consiguiendo un incremento de la velocidad de transferencia, que conseguir
tecnologías de semiconductores nuevas con un tiempo de latencia más
pequeño. Es decir, la forma más fácil de reducir la penalización de fallos de la
memoria cache es incrementar el número de palabras por ciclo de reloj que se
pueden transferir entre ésta y la memoria principal.
 Una primera organización que incrementa el ancho de banda de la
memoria consiste en aumentar la anchura de la memoria principal, es decir, el
número de bytes que proporciona la memoria en cada acceso. De este modo,
en un mismo ciclo de reloj se transfiere un mayor número de bytes entre los
dos niveles de la jerarquía, reduciendo el tiempo de transferencia. Sin
embargo, esta estrategia posee una serie de inconvenientes:
1.- En primer lugar aumenta el coste del bus de datos, puesto que se
debe incrementar su anchura para permitir la transmisión de los bytes tras
cada acceso a memoria principal.
2.- Existe un conflicto entre los accesos de la CPU a la cache y el
ancho de banda de memoria. Normalmente estos accesos poseen el tamaño de
una palabra. Como la memoria cache debe poder soportar un ancho de banda
mayor, ha de existir un punto de multiplexación entre la memoria principal y
la CPU, provocando la aparición de retardos. Es decir, la cache debe poder
leer y escribir datos en el bus de forma que se pueda realizar la transferencia
respetando el ancho de banda requerido. Una primera alternativa sería
implementar una memoria cache con la misma anchura que la memoria
principal. En este caso, el punto de multiplexación se encontraría entre la
CPU y la cache. Pero esta organización es la que provoca la aparición de
retardos mayores. Por ello y siempre que la cache sea lo suficientemente
rápida en comparación con el bus, se puede implementar ésta con anchura de
una palabra y conectar el multiplexor entre el bus y la cache.
A continuación presentamos un esquema en el que se describe este
tipo de organización. A la izquierda se ilustra un esquema de memoria ancha
en el que el punto de multiplexación se encuentra entre la cache y la CPU,
mientras que en la derecha se muestra una organización en la que la cache es
mucho más rápida que el bus y el multiplexor se ubica entre ambos.
Una segunda organización, conocida como memoria entrelazada,
también permite el incremento del ancho de banda. La idea consiste en
solapar en el tiempo los accesos que sobre la memoria principal realiza la
cache. La memoria principal se divide en distintos bancos. Se incrementa el
ancho del bus de direcciones, pero no el ancho de la memoria principal, de la
cache ni del bus de datos. Se pueden enviar varias direcciones al mismo
tiempo, cada una dirigida hacia un banco en concreto. De esta forma, el
tiempo de latencia de un conjunto de accesos es equivalente al de sólo uno,
reduciendo el número de ciclos de penalización de fallo de la cache. Tras
realizarse los accesos, las palabras son transferidas secuencialmente por el bus
de datos entre los dos niveles de la jerarquía.
Según la correspondencia o factor de entrelazado entre las direcciones
y los bancos, se tienen tipos diferentes de memoria entrelazada. El tipo más
utilizado y para el que esta organización fue concebida inicialmente es el de
memoria entrelazada por palabras. Para entender el entrelazado por palabras,
supongamos que los bancos están numerados desde cero hasta N-1, siendo N
el número total de bancos. El banco M contendrá aquellas palabras cuya
dirección módulo N sea M.Este tipo de entrelazado permite una mejora
importante del rendimiento de la memoria cuando soporta accesos
secuenciales, ya que las palabras se encuentran en distintos bancos y un
mayor número de ellas pueden ser accedidas simultáneamente. Como los
fallos de lectura en cualquier tipo de cache y las escrituras en memorias cache
Memoria principal
Cache
CPU
Multiplexor
Bus
Memoria principal
Cache
CPU
Bus
Multiplexor
de postescritura se realizan por bloques de palabras consecutivas, se puede
sacar partido de este entrelazado.
Sin embargo, la memoria entrelazada también posee sus
inconvenientes:
1.- La facilidad para poder dividir la memoria en un conjunto de
bancos entra en conflicto con la capacidad de los chips de memoria. Cada
chip de la memoria está formado por una matriz de celdas, cada una de las
cuales capaz de almacenar un bit. Los chips poseen terminales eléctricos que
permiten direccionar una fila de la matriz para leer o escribir datos en ella. El
número de bits de cada fila puede variar de uno a la longitud total de la
palabra (32 bits en la mayoría de computadoras). Cuanto más grandes sean los
chips de memoria, es decir, cuantas más filas posea su matriz, menor
flexibilidad se tiene para distribuirlos de forma que formen bancos.
Supongamos, por ejemplo, que queremos construir una memoria de 16 MB,
que la matriz de celdas de los chips posee un bit por fila y que se almacenan
palabras de 32 bits. Si los chips son de 256 KB, podemos construir 16 bancos
de 32 chips cada uno. Sin embargo, si los chips son de 4 MB, solo podemos
construir un banco con 32 chips, desaprovechando memoria de los chips.
Como la tendencia es construir chips con mayor tamaño, disponer de una
memoria entrelazada es cada vez más costoso, ya que un mismo número de
bancos supone un incremento muy grande de la capacidad de la misma.
2.- Normalmente, el hardware de control de los sistemas de memoria
entrelazada precisa de bancos del mismo tamaño, por lo que incrementar la
memoria supone incrementar el tamaño de todos ellos. Como la diferencia de
capacidad entre distintos chips es muy grande, debido a la tendencia a
hacerlos cada vez mayores, un incremento mínimo de la memoria supone, en
la mayoría de los casos, la duplicación de la misma.
A continuación presentamos un esquema en el que se muestra la
distribución de un sistema típico de memoria entrelazada. La memoria
principal se divide entre cuatro bancos distintos y tanto el bus como la cache
tienen la anchura de un banco.
4.- Relación entre la memoria virtual y la cache
El siguiente nivel en la jerarquía después de la memoria principal está
formado por los discos, es decir, por el almacenamiento secundario. La
memoria secundaria es fundamental para permitir la ejecución de múltiples
procesos en un sistema. Cada proceso posee un espacio de direcciones sobre
el que está trabajando. Sin embargo, a cada uno de ellos se le asigna una
cantidad de memoria principal menor a su espacio de direcciones. Para ello
ambos se dividen en bloques, de manera que sólo una parte de los que forman
el espacio de direcciones se cargan en los de memoria principal. Si un proceso
hace referencia a una dirección que no está en ninguno de los bloques de
memoria principal que le fueron asignados, reemplaza uno de esos bloques
por el que contiene la dirección a la que hizo referencia. De esta forma, el
proceso sólo posee en memoria principal los bloques necesarios para su
ejecución, mientras que el resto está ubicado en memoria secundaria. Pero el
hecho de disponer de memoria secundaria permite, además, que los
programas puedan disponer de un espacio de direcciones mayor que el de
memoria principal. Disponemos, así, de un sistema de memoria virtual.
Para entender la relación de la memoria virtual con la cache debemos
tener en cuenta la forma en que se localizan los datos en memoria principal.
Existen básicamente dos tipos de memoria virtual. Los sistema paginados son
aquellos en los que los bloques son de tamaño fijo, mientras que en los
CPU
Cache
Bus
Banco de
memoria 0
Banco de
memoria 1
Banco de
memoria 2
Banco de
memoria 3
segmentados son de tamaño variable. Las direcciones virtuales se dividen en
dos partes. La parte más significativa de la dirección especifica el número de
bloque, página o segmento según el caso, mientras que la menos significativa
determina el desplazamiento dentro del bloque. Para realizar la traducción de
la dirección virtual a dirección física, el sistema operativo mantiene para cada
proceso una tabla de bloques. La tabla se indexa con el número de bloque y
para cada entrada posee un bit que determina si éste está cargado o no en
memoria principal. En caso de que el bloque esté disponible, también contiene
la dirección física del mismo.
Ya empezamos a ver cual es el nexo entre la memoria virtual y la
cache. Para determinar si existe un acierto o fallo de cache, la dirección debe
ser traducida previamente. Las tablas de bloques suelen ser muy grandes y se
ubican en memoria principal, incluso pueden estar ellas mismas paginadas o
segmentadas. Cualquier acceso a memoria tiene un coste excesivo, ya que
debe pasar antes por la tabla y traducirse, produciendo un acceso a memoria
principal.
La primera solución a este problema consiste en construir una pequeña
cache que contenga parte de la tabla de bloques. Confiando en el principio de
localidad, se almacenan en ésta pequeña tabla las entradas de la tabla de
bloques a las que se han accedido más recientemente. Está cache se conoce
con el término de buffer de traducciones anticipadas o TLB.
Aunque el uso del TLB suponga un incremento de la velocidad de
acceso a la memoria, la comprobación de las etiquetas de la cache debe
esperar a que el TLB dé su resultado. Para conocer el valor de acierto de la
cache de forma más rápida podemos realizar el acceso al TLB y a la cache de
forma simultánea. Para ello, de la dirección que produce la CPU se toma la
parte de la etiqueta como el número de bloque, página o segmento,
accediendo con ella al TLB. A su vez, se accede a la cache con el resto de la
dirección. Como la traducción a través del TLB es más rápida que la
obtención de la etiqueta en la cache, cuando se dispone de ésta ya se puede
realizar la comparación para determinar el acierto o fallo de cache. Sin
embargo, existe una limitación de tamaño para las cache de correspondencia
directa. Como el acceso a la cache se realiza con la parte de la dirección que
especifica el desplazamiento dentro del bloque donde se encuentra el dato, el
tamaño de la misma no puede exceder el tamaño de bloque. Por ello, si se
quieren tener memorias cache de mayor capacidad se ha de aumentar la
asociatividad y comparar la salida del TLB con las etiquetas de cada una de
las vías paralelamente.
Una segunda posibilidad es disponer de una cache virtual. En este
caso, se accede a la cache con las direcciones virtuales y se comparan las
etiquetas directamente sin pasar por el TLB. Sólo se accederá al TLB en caso
de que se dé un fallo de cache. Pero este tipo de cache posee dos
inconvenientes:
1.- En un entorno de multiprogramación se realizan continuamente
cambios de procesos. Esto hace que una misma dirección virtual apunte a
zonas de memoria distintas para procesos diferentes. Por ello, la cache debe
ser limpiada, purgase, cuando se cambia de proceso. Una solución a este
problema se basa en el hecho de que el sistema operativo identifica a cada
proceso de forma única con un número en concreto, conocido como PID. Este
PID se incluye en las etiquetas de la cache para determinar a qué proceso
pertenece cada bloque. De esta forma, no hace falta limpiar la cache con cada
cambio de proceso, si bien se debe tener en cuenta que un fallo de cache
también puede producirse porque el PID de la etiqueta no se corresponde al
del proceso que está accediendo a la misma.
2.- En algunos sistema operativos se permite que una aplicación pueda
referenciar el mismo dato con dos direcciones virtuales distintas, conocidas
como alias. Los aliaspueden producir la replicación de un mismo bloque en
la cache virtual, porque la dirección no está traducida a su valor físico.
Existen implementaciones hardware que impiden la duplicación de bloques
en la cache, si bien algunos sistemas operativos pueden forzar a que los alias
compartan el número de bits menos significativos suficientes para evitar la
duplicación de bloques. Por ejemplo, si poseemos una cache de
correspondencia directa con un tamaño igual o inferior a 218 bytes, basta que
los 18 bits menos significativos de los alias coincidan.
A continuación se muestra una gráfica que representa la frecuencia de
fallos frente al tamaño de la cache virtual para un sistema de
monoprogramación, un sistema de multiprogramación con gestión de PID en
la cache y un sistema de multiprogramación con purga.
Como se puede observar, para cualquier tamaño gestionar el PID en la
cache virtual produce la menor frecuencia de fallos. La purga es un buen
método cuando el tamaño de la cache virtual es pequeño, pero su efectividad
disminuye ligeramente con el aumento del mismo. Por su lado, cuando se
permite la ejecución de un solo proceso, la frecuencia de fallo disminuye con
el aumento de la capacidad de la cache virtual, ya que un número mayor de
sus bloques estarán ubicados en ella. Estas estadísticas fueron obtenidas tras
un estudio realizado por Agarwal en 1987. Se utilizó el sistema operativo
Ultrix, corriendo en un VAX y utilizando memorias cache de correspondencia
directa con un tamaño de bloque de 16 bytes.
0
5
10
15
20
Frecuencia de
fallos %
2 4 8 16 32 64 128 256 512 1024
Tamaño de la cache virtual (KB)
Frecuencia de fallos frente a tamaño de cache
virtual
Purga
PIDs
Uniproceso
5.- Conclusión
La jerarquía es una organización que permite aumentar el rendimiento
del sistema de memoria. Se intenta que este rendimiento sea lo más parecido
posible al del nivel más rápido. Como el nivel más rápido de todo el sistema
después del procesador es la memoria cache, el diseño de las otras partes del
sistema de memoria, tales como la memoria principal y la virtual, debe
realizarse de tal forma que aumente el rendimiento de la misma.
Para ello se buscan organizaciones de las distintas partes de la
jerarquía que permitan reducir la penalización y el tiempo de acierto de la
cache.

Continuar navegando