Tuning Solaris: Memoria Cache

La cache es una memoria temporal de acceso rápido, utilizada para almacenar datos y evitar de esta forma tener que acceder a la memoria principal que es mucho más lenta. Esta podría ser una definición para la memoria cache y aunque este no es el mejor ámbito para estudiar la memoria cache, ya que al ser la cache parte del procesador, normalmente se estudia como parte de la arquitectura de procesadores, pero siempre he pensado que un administrador no solo tiene que conocer las aplicaciones que corren en el sistema, sino que debería conocer como funciona el sistema y el funcionamiento de la cache es parte importante de nuestro sistema, por lo tanto, pienso que, aunque no los veamos en profundidad, si debemos tener unas nociones sobre como funciona la memoria cache.

No vamos a profundizar en la arquitectura de las caches, sobre todo porque cada procesador implementa un tipo de cache y esta arquitectura va cambiando de un procesador a otro, pero si se realizará un recorrido por los conceptos básicos de este tipo de memoria y como el Kernel accede a ella. De todas formas, como ejemplo he tomado el procesador de Sun UltraSPARC IV+ que a día de hoy es uno de los más extendidos.
Niveles de Cache (Jerarquía de cache)
No se puede hablar de Cache, como un elemento único, ya que lo que se conoce comúnmente como memoria caché está formada por una serie de elementos que se organizan en una jerarquía, esta jerarquía está formada por varios niveles de memoria:
Nivel superior, es la memoria que está más cerca del procesador, es de pequeño tamaño pero extremadamente rápida.
Nivel inferior, este nivel se sitúa entre la memoria de nivel superior y la memoria principal del sistema, es más lenta que la de nivel superior aunque como ventaja presenta que es de mayor tamaño.
Cuando se habla de memoria cache no se identifica a los niveles de la jerarquía como se ha comentado antes, ya que esta ha sido una definición amplia, en la bibliografía existente sobre procesadores, a estos niveles de jerarquía, se les denominan L1, L2 y L3. Dependiendo del tipo de procesador con el que se esta trabajando, el tamaño y número de niveles varía, así en el procesador como el UltraSparc IV+ existen 3 niveles en la jerarquía de cache.
Nivel L1
Es la memoria más rápida con la que trabaja el procesador, aunque presenta el inconveniente de su reducido tamaño, el procesador dispones de 4 tipos de cache L1:
I-Cache, es la cache que se utiliza para las instrucciones, su tamaño es de 64KB.
D-Cache, consiste en la cache utilizada para los datos y su tamaño también es de 64KB.
P-Cache, esta cache tiene un tamaño de 2KB y se utiliza para la prelectura.
W-Cache, es la cache utilizada para las escrituras, su tamaño solo de 2KB.
NOTA – El procesador UltraSparc IV+ dispone de 2 cores, cada core tiene 4 de estas caches.
Nivel L2
El segundo nivel de jerarquía lo conforma la cache L2, es más lenta que la cache L1 aunque presenta la ventaja que es mayor tamaño. En el procesador UltraSPARC IV+ la cache L2 se encuentra dentro del chip, porque es compartida por los 2 cores y tiene un tamaño de 2MB.
Nivel L3
L3 es el tercer nivel de la jerarquía, es la cache más lenta y está situada fuera del chip, el tamaño de esta cache en el procesador UltraSPARC IV+ es de 32MB. La implementación de L2 y L3 son totalmente excluyentes, esto quiere decir que
si algo está en L3 no estará en L2 y viceversa, se puede decir que, como L2 no está incluida en L3 el tamaño de cache es de 34MB, 32MB de L3 más 2MB de L2.
NOTA – El único procesador de la serie UltraSPARC que implementa una cache L3 es el IV+. Siempre se han implementado jerarquías de 2 niveles y hasta el nuevo procesador T1 carece de una cache L3.
Funcionalidades de las caches
En el apartado anterior se ha visto la forma en la que la cache se organiza en una jerarquía y como cada uno de los niveles de esta jerarquía, están formados por unos tipos de cache, los cuales tienen unas funciones determinadas.
I-Cache
Este tipo de cache se utiliza para almacenar instrucciones y evitar de esta forma que se tenga que acceder a memoria para traer la instrucción. El disponer de una cache especifica para instrucciones permite implementar métodos de predicción de instrucciones, estos métodos realizan una predicción sobre la próxima instrucción a ejecutarse, de esta forma mientras el procesador está ejecutando un instrucción, en paralelo está almacenando en la cache de instrucciones la que puede ser la siguiente instrucción, en caso de acierto se evita el perder ciclos de CPU en acceder a memoria.
D-Cache
La cache de datos se utiliza para almacenar los datos con los que debe operar el procesador, cuando una instrucción necesita un dato, lo primero es comprobar que dicho dato se encuentra en la cache, en caso de que no sea así, se obtiene de la memoria y se almacena en la cache de datos, de donde será copiado al registro correspondiente para que el procesador pueda trabajar con el. Los datos siempre pasan por la cache.
P-Cache
Hoy día la mayoría de los procesadores, implementan métodos para la prelectura de información, estos métodos se basan en intentar predecir, qué será lo necesite el procesador para ejecutar la siguiente instrucción, la P-cache (la P es de la palabra prefetch) se utiliza para almacenar la información de prelectura. Como los métodos de prelectura no son cien por cien fiables, es absurdo dedicar una cache grande para esto, por eso los tamaños de este tipo de caches suelen ser pequeños.
W-Cache
La utilización de caches presenta una ventaja frente al acceso directo a la memoria del sistema, y es que las caches son mucho más rápidas que una memoria normal, pero presentan varios inconvenientes, uno de estos inconvenientes es solucionar el problema de las escrituras sobre las caches, los procesadores implementan un método de escritura basado en una cache de
escritura (w-cache) en la que se van almacenando todas las escrituras para posteriormente actualizar las páginas de memoria con los datos modificados. La cache W-Cache implementa su política de reemplazo como una FIFO.
I-TLB
Los procesadores no trabajan con direcciones físicas de memoria sino que lo hacen con direcciones virtuales, el procesador UltraSPARC IV+ trabaja con direcciones de 64bits, las cuales se tienen que convertir a direcciones de 43bits, esta conversión produce un coste en la ejecución de cualquier instrucción, existe una cache llamada I-TLB (Translation Lookaside Buffer) que se encarga de almacenar las conversiones de las últimas instrucciones ejecutadas.
D-TLB
Igual que existe un tipo de cache para almacenar las conversiones de las direcciones virtuales a direcciones físicas de las instrucciones, también existen caches para almacenar las conversiones de las direcciones de datos.
Hemos dado un repaso a los distintos tipos de caches que nos podemos encontrar en nuestro sistema, ya hemos mencionado antes, que tanto los niveles de jerarquía como el número de caches dependerá de la arquitectura del procesador, pero básicamente, en todos encontraremos más o menos lo mismo. Un poco más adelante veremos como podemos consultar estadísticas de las distintas caches del Sistema para intentar identificar un problema de perdida de rendimiento causado por el acceso a las caches.
Page coloring
Los algoritmos denominados Page coloring se utilizan para decidir qué página de la memoria física se va a asignar a un espacio de direcciones de un proceso, cuando éste la demanda. Para reducir los problemas de rendimiento en el acceso a memoria, se intenta cumplir el principio de proximidad “la página seleccionada debería estar cerca físicamente de la página anterior”. Los algoritmos de coloreado de página (page coloring) aplican las normas citadas anteriormente, pero desde el punto de vista de la memoria cache, es decir, no importa que las páginas de memoria física estén continuas, mientras que lo estén en la memoria cache.
La memoria más rápida con la que trabaja el procesador, como ya sabemos, es la memoria cache y en una situación ideal, todos los datos y direcciones que el procesador necesitase para ejecutar un conjunto de instrucciones deberían estar almacenadas en alguna parte de la jerarquía de caches, pero esta, no deja de ser una situación ideal, ya que la cache no es lo suficientemente grande como para almacenar todas las páginas de la memoria física. Un punto importante donde se pierden bastantes ciclos de CPU es en el trasiego de páginas entre la memoria física y las caches, cuanto menor sea dicho trasiego más instrucciones podrán ejecutarse en el procesador y por lo tanto más velocidad se conseguirá en la máquina.
Suponemos que el sistema dispone de una cache de 1024K, la transferencia entre la cache y la memoria física se realiza mediante páginas del tamaño de 8K, esto permite que en la cache puedan estar 128 páginas a la vez, los algoritmos de coloreado de páginas lo que hacen es coger la lista de páginas físicas libres y asignarles un color a cada página, habrá tantos colores como el tamaño de la caché divida por el tamaño de página, en el ejemplo dispondremos de 128 colores, uno por cada página de la cache, el algoritmo utiliza estos colores para reemplazar las páginas en la cache. Los colores son asignados a las páginas cuando éstas son liberadas. Lo que intentará el algoritmo es que en el espacio de direcciones de un proceso se repitan el menor número posible de colores, ya que dos páginas de un mismo color tienen asociada la misma página de cache.
Existen varios algoritmos de reemplazo mediante páginas coloreadas, Solaris implementa tres de ellos:
Correspondencia entre dirección física y virtual, el color de página física elegido para una determinada página virtual estará directamente relacionado con el mapeo de la dirección virtual.
Función hash para la dirección virtual, se utiliza un algoritmo hash para repartir las páginas por toda la cache.
Bin hopping, se utiliza una función de round robin para asignar la página física.
El algoritmo por defecto utilizado por Solaris es el de la función hash para las direcciones virtuales. El parámetro del kernel que define el tipo de algoritmo de coloreado de página es consistent_coloring, se puede cambiar en caliente y los valores
asignados a los distintos algoritmos son el 0 para la función hash, el 1 para el que utiliza la correspondencia entre direcciones físicas y virtuales, y el 2 para el de Bin hopping.
Con mdb podemos consultar qué algoritmo de page coloring está funcionando y cambiarlo para ver si mejoran los accesos de cache en nuestro sistema.

(root@camtrunet)# mdb -k
Loading modules: [ unix krtld genunix ip usba ipc random nfs ptm cpc ]
> consistent_coloring/D
consistent_coloring:
consistent_coloring: 0
> consistent_coloring/W 1
mdb: failed to write 1 at address 0x14311d0: target is not open for writing
> $q

(root@camtrunet)# mdb -kw
Loading modules: [ unix krtld genunix ip usba ipc random nfs ptm cpc ]
> consistent_coloring/W 1
consistent_coloring: 0 = 0x1
> consistent_coloring/D
consistent_coloring:
consistent_coloring: 1
>
>
Una vez que hemos cambiado el algoritmo de page coloring tendremos que consultar las estadísticas sobre accesos a cache, esto lo veremos en el siguiente punto.
¿Problemas en las caches?
Los procesadores UltraSPARC disponen de una serie de contadores hardware que permiten monitorizar la actividad de parte de los elementos del procesador. Existen contadores que registran la actividad sobre las caches, esto permite conocer qué está ocurriendo con las mismas. El comando cpustat devolverá el valor de varios de estos contadores, la sintaxis del comando se puede ver en el apartado de comandos.
NOTA – Cada tipo de procesador tiene un conjunto de contadores propios, aunque existen algunos contadores que están en la mayoría de procesadores, otros son específicos de cada procesador. Es importante ver qué contadores está disponible en nuestro procesador.
En la siguiente tabla se muestran algunos de los contadores que registran eventos de las caches:
ITLB_miss Fallos en la cache I-TLB Un número elevado de fallos en la cache I-TLB indican que se está utilizando una gran cantidad de memoria para la ejecución de las instrucciones, cuyas direcciones nos se pueden mantener en la cache TLB, por lo que sería conveniente aumentar el tamaño de página, ya que el no disponer de la dirección del dato en la cache produce trasiego entre la memoria y la cache.
DTLB_miss Fallos en la cache D-TLB Un número elevado de fallos en la cache D-TLB indican que se está utilizando una gran cantidad de memoria, cuyas direcciones nos se pueden mantener en la cache TLB, por lo que sería conveniente aumentar el tamaño de página, ya que el no disponer de la dirección del dato en la cache produce trasiego entre la memoria y la cache.
L2_IC_miss Número de fallos en la cache de instrucciones de L2.
L3_IC_miss Número de fallos en la cache de instrucciones de L3.
Re_DC_miss Número de ciclos perdidos porque el dato no se encontraba en la cache L1.
Re_EC_miss Número de ciclos perdidos porque el dato no se encontraba en la cache L2.
Rstall_storeQ Número de ciclos perdidos mientras se termina una operación de escritura en la W-Cache
Esta lista de eventos es útil para intentar determinar un posible problema con la caches, pero no existen unos valores umbrales a partir de los cuales se pueda considerar que existe un problema en la gestión de la cache, sino que debe realizarse un análisis basado en el tipo de máquina, número de procesadores, tipo de aplicaciones, etc. Por lo que no se pueden dar unos valores umbrales que sirvan de patrón para la identificación de problemas, ya que esto puede ocasionar que se produzca un diagnóstico erróneo. Es preferible analizar durante un tiempo los distintos parámetros de ejecución para poder observar las posibles anomalías.
Para determinar un posible problema se tendrá que analizar varios de los eventos de la tabla anterior, supongamos que durante un periodo de tiempo el sistema ha funcionado correctamente y a partir de un momento determinado comienza a degradarse el rendimiento, se deberían analizar los distintos contadores de la cache para descubrir un problema en ellas.
Los problemas de rendimiento generados por las caches no suelen ser fácilmente solucionables, ya que no debemos olvidar, que las caches son de un tamaño fijo y no se puede realizar ningún tipo de control sobre ellas, por lo tanto solo los problemas en la caches TLB tienen cierta solución.
TSB
como sabemos, la TLB se encuentra en el procesador, esto no permite que sea de un tamaño suficiente para que sea eficaz. Los procesadores UltraSPARC utilizan un método de traducción de direcciones que combina elementos HW como la MMU (Memory Management Unit) con elementos SW del SO, uno de estos elementos gestionados por el SO es la TSB (Translation Storage Buffer), que es una tabla mucho más grande que las TLBs, pero cuyo acceso es bastante más lento, por encontrarse en memoria principal.
la traducción de una dirección virtual a una dirección física suele hacerse en un ciclo de CPU, si dicha traducción se encuentra en laguna de las TLBs, pero si el sistema debe buscar la traducción en la TSB, el proceso se puede alargar varios ciclos de CPU, ya que el acceso a la memoria principal donde se encuentra dicha tabla es una acceso normal a memoria, con el gasto de ciclos de CPU que esto supone.
El proceso de traducción de direcciones virtuales a direcciones físicas, a groso modo, sería el siguiente, el procesador genera una petición de página para una dirección virtual de 64bits, la petición llega a la MMU que transforma, la dirección, en una dirección de 43bits, para ello, comprueba que la traducción de la dirección de la página a la que pertenece la dirección que se quiere traducir se ha realizado con anterioridad y está en la TLB, en caso de que no la encuentre se produce un fallo de TLB, lo que provoca que se tenga que buscar en la TSB, si se encuentra una entrada en la TSB se utiliza para convertir la dirección virtual en dirección física y dicha información se refleja en la TLB, ya que por el principio de localidad, es muy posible que en breve se vuelva a pedir una traducción de una dirección que pertenezca a la misma página.
No suelen ser demasiado frecuente, pero puede darse el caso que la dirección de página que se busca no se encuentre en la TSB, esto se conoce como fallo de TSB, en este caso, el sistema debe buscar la página recorriendo las distintas estructuras de datos que mantienen esta información, lo que supone el accesos a memoria principal para consultar estos datos y por lo tanto el uso de una gran cantidad de ciclos de CPU hasta que sea encontrada la página, una vez que ha
sido encontrada, se incluye en la TSB debido al principio de localidad.
Tanto las TSBs como las TLBs, son tablas que funcionan como caches, las entradas de estas tablas se conocen como TTE (Translation Table Entry). Una TTE está formada por dos componentes, una marca de la dirección virtual de la página y su traducción en dirección física. La marca de la dirección virtual la forman la propia dirección virtual de la página y el número del contexto asociado al proceso.
En las versiones de Solaris anteriores a la 10, las TSBs forman un pool de tablas compartidas por todos los procesos y con unos tamaños fijados, 128KB o 512KB, que se fijaban en el momento del arranque. Solaris 10 implementa soporte para TSB dinámico, lo que permitirá entre otras cosas, disponer de unas TSBs por cada tamaño de página y permitiendo aumentar o disminuir el número de TTEs que almacena. Y otro mejora es que las TSB estarán asociadas a cada proceso, evitando que sean compartidas por varios, evitando así que se almacene información de un proceso, que es totalmente innecesaria para otro. Disponer de soporte TSB dinámico hace que Solaris 10 sea mucho más eficiente, ya que se producirán muchos menos fallos de TSB, y por lo tanto no se perderán tantos ciclos de CPU para mantener las TSBs.
trapstat
Ya conocemos, aunque sea a groso modo, los distintos tipos de caches que podemos encontrar en nuestro sistema, ahora vamos a ver cómo podemos conocer qué está ocurriendo con las caches del sistema. El comando trapstat, como podemos leer en el man, devuelve una serie de informes sobre traps de nuestro sistema, de toda la información que podemos obtener de trapstat, hoy solo vamos ver a que se refiera a las TLB y TSB, para ello ejecutaremos el comando con el parámetro -t.

(root@camtrunet)# trapstat -t
cpu m| itlb-miss %tim itsb-miss %tim | dtlb-miss %tim dtsb-miss %tim |%tim
-----+-------------------------------+-------------------------------+----
0 u| 13379 0.4 98 0.0 | 58626 1.6 3725 0.3 | 2.3
0 k| 807 0.0 85 0.0 | 72914 2.4 1703 0.2 | 2.7
-----+-------------------------------+-------------------------------+----
1 u| 16288 0.5 129 0.0 | 61008 1.7 2774 0.2 | 2.5
1 k| 806 0.0 93 0.0 | 63922 2.1 1762 0.2 | 2.4
=====+===============================+===============================+====
ttl | 31280 0.5 405 0.0 | 256470 3.9 9964 0.5 | 4.9

...

cpu m| itlb-miss %tim itsb-miss %tim | dtlb-miss %tim dtsb-miss %tim |%tim
-----+-------------------------------+-------------------------------+----
0 u| 12645 0.4 126 0.0 | 46723 1.3 3852 0.3 | 2.0
0 k| 743 0.0 12 0.0 | 70490 2.3 1763 0.2 | 2.6
-----+-------------------------------+-------------------------------+----
1 u| 9827 0.3 73 0.0 | 37525 1.1 3528 0.3 | 1.7
1 k| 491 0.0 26 0.0 | 104524 3.3 2600 0.3 | 3.6
=====+===============================+===============================+====
ttl | 23706 0.4 237 0.0 | 259262 4.0 11743 0.6 | 4.9
La salida del trapstat devuelve una matriz como la anterior, la cual tiene varias columnas.
itlb-miss fallos en la TLB de instrucciones.
itsb-miss fallos en la TSB de instrucciones.
dtlb-miss fallos en la TLB de datos.
dtsb-miss fallos en la TSB de datos.
La matriz presenta 2 filas por cada CPU del sistema, una de estas filas es para las operaciones de usuario (u) y otra para las de Kernel (k). La última fila de cada matriz es la suma de cada columna. Con esta información podemos analizar qué está ocurriendo con las caches TLB y TSB en nuestro sistema, si se está perdiendo mucho tiempo con fallos de instrucciones o datos y conociendo como interactúan las distintas caches, podemos comprender como está afectando al sistema el que se produzcan fallos en una u otra cache.
Vamos a ver, de forma genérica, algunas de las situaciones que nos podríamos encontrar:
itlb-miss, un número alto de fallos de este tipo, durante un largo periodo de tiempo, nos indica que hay un problema con la cantidad de información que se está solicitando a las caches de instrucciones, se están ejecutando una gran cantidad de instrucciones, habría que aumentar el tamaño de las páginas destinadas a almacenar código.
dtlb-miss, muchos fallos en la TLB de datos nos indica que existe un problema con el tamaño de las páginas destinadas para almacenar datos. Una solución sería cambiar el tamaño por defecto, para aumentar la cantidad de datos almacenada por una página y de esta forma disminuir el número de fallos en TLB.
tsb-miss, independientemente de que los fallos en TSB se produzcan en la instrucciones como en la de datos, el tener muchos fallos durante un periodo prolongado de tiempo, significa que tenemos un verdadero problema con el rendimiento de nuestro sistema, ya que el procesador debe perder muchos cliclos de CPU, para tratar primero el fallo de la TLB y posteriormente el fallo en la TSB.
cpustat
No vamos a repetir lo que se dijo en el artículo Rendimiento/Tuning Solaris: Introducción sobre los comandos cpustat y cputrack, pero aunque sirva solo de recordatorio, con estos dos comandos podemos consultar los contadores que los procesadores guardan con información como el número de fallos en las caches de instrucciones o en las de datos. Una vez que hemos comprobado con el comando trapstat que tenemos un problema con el rendimiento debido al acceso a las caches, podemos utilizar el comando cputrack para localizar el proceso que está causando la perdida de rendimiento en el acceso a las caches.
Podemos utiliza cpustat para ver cuantos fallos se producen en la cache de instrucciones, en la de datos, en lecturas o escritura de las caches de datos, cuantos fallos se producen en las TLBs de datos o instrucciones, por cada ciclo de ejecución o por el número de instrucciones. Vamos a realizar un sencillo ejemplo para ver como se está comportando las escrituras en la cache de datos.

(root@camtrunet)# cpustat -c pic0=Instr_cnt,pic1=DC_rd_miss 1 5
time cpu event pic0 pic1
1.008 1 tick 126881 631
1.008 0 tick 154408 1965
2.008 0 tick 17643752 102257
2.008 1 tick 31269729 116268
3.008 1 tick 87138 836
3.008 0 tick 169335 1963
4.008 1 tick 354225 2005
4.008 0 tick 617257 5338
5.008 1 tick 7892399 65985
5.008 0 tick 3266275 30710
5.008 2 total 61581399 327958
(root@camtrunet)#
Podemos ver, que para esta sencilla prueba, no parecen existir problemas, de todas formas, para analizar este tipo de datos debemos realizar muestras más largas, ya que cinco segundos no es un periodo demasiado amplio.

Comentarios

Entradas populares de este blog

Comandos Solaris

MikroTik QoS Script generator

Comando para eliminar saltos de linea en fichero o archivo desde consola Linux