Tuning Solaris: Múltiples tamaños de página

Una de las características más interesantes de Solaris, en cuanto a la gestión de la memoria es MPSS (Multiple Page Size Support) o Soporte para múltiples tamaños de páginas, básicamente, consiste en que podemos decirle al Kernel cual es el tamaño de página que queremos que se asignen a nuestros procesos. Cómo pudimos ver en el artículo Rendimiento/Tuning Solaris: Memoria Cache los tiempos de accesos a las caches y a la memoria del sistema, son variables que debemos tener muy en cuenta a la hora de realizar un análisis del rendimiento de nuestro sistema.
En este artículo veremos como MPSS puede ayudarnos para aumentar el rendimiento de nuestro sistema, no se pretende dar una guía que seguir como un guión, sino más bien unas nociones que nos ayuden a entender como MPSS nos puede ayudar. No esperes encontrar la formula mágica de todos los problemas de rendimiento, ya que, como intentaremos explicar más adelante, quizás tu sistema no necesite de MPSS y emplear esta característica pueda tener un resultado totalmente contrario al deseado, más adelante veremos algunos ejemplos.
Tamaño de página
La memoria física del sistema se organiza en unidades de un tamaño fijo llamadas páginas, dependiendo de la arquitectura de nuestro sistema tendremos una serie de tamaños de páginas. Por defecto el tamaño de página de es 8K.
(root@camtrunet)# pagesize -a
8192
65536
524288
4194304
(root@camtrunet)#
El espacio de direcciones de un procesos se organiza en unidades lógicas, llamadas segmentos, todos los procesos disponen de al menos 4 segmentos. Cada uno de los segmentos es utilizado por el proceso para una determinada función, como el segmento de texto, que se utiliza para almacenar las instrucciones que debe ejecutar el proceso, o el segmento de datos, utilizado por el proceso para los datos. Por lo tanto cada segmento tiene una función distinta y por lo tanto el acceso a las páginas de que forman cada segmento no es el mismo.
En una aplicación que realice muchos cálculos sobre pocos datos, el sistema tendrá que acceder muchas mas veces al segmento de texto, para leer las instrucciones que hay que ejecutar que al segmento de datos o al segmento heap. En cambio en una aplicación en la que se trabaje sobre una gran cantidad de datos, el sistema accederá muchas mas veces a la páginas donde se almacenan los datos, que a las páginas donde se almacenan las instrucciones.
Accesos a memoria
Podemos decir, que a nivel de rendimiento, los tiempos de accesos del procesador a la memoria física de la máquina, al menos en sistemas SMP (symmetric multiprocessing), es un valor constante. Existen fórmulas para reducir los tiempos de accesos a los datos, pero no tenemos que confundir con el tiempo de acceso a la memoria, según la arquitectura de nuestro sistema, el dato puede que esté mas lejos o más cerca del procesador, la formulas para reducir los tiempos de acceso a los datos pasan por utilizar una serie de caches, que almacenen los datos de forma temporal.
Para facilitar nuestro análisis vamos a entender que el tiempo que tarda el procesador en acceder a una posición de memoria física es constante, partiendo de esta hipótesis podemos centrar nuestro estudio en las veces que el procesador debe acceder a la memoria físicia, generando la siguiente regla.
Contra menos veces tenga que acceder el procesador a la memoria física, mayor será el rendimiento.
Podemos explicar esta regla, como que cuanto menos accesos haya ha memoria, menos se ocuparan los buses de accesos y más tiempo estarán disponibles para otra operación. En sistemas multiprocesos, es importante evitar el uso excesivo e innecesario de un recurso, ya que podría afectar a otro proceso.
Una de las fórmulas para disminuir el número de accesos del procesador a las página de memoria física, consiste en emplear, tal como se puede ver en el artículo Rendimiento/Tuning Solaris: Memoria Cache, varios niveles de memoria cache. Una de esta cache es la TLB (translation lookaside buffer) que se utiliza para almacenar la conversión entre la dirección virtual de una página y su dirección física, esta cache tiene una tamaño determinado, por lo que solo puede almacenar una cantidad limitada de traducciones. Si la cache tiene un tamaño de 16 posiciones y el sistema emplea páginas de 8k, con la información almacenada en la cache TLB podemos acceder a 16 * 8K direcciones de memoria. El segundo nivel de cache de traducción de direcciones virtuales a direcciones físicas es la TSB (Translation Storage Buffer) que tiene mayor tamaño que la TLB, pero presenta un inconveniente, está almacenada en memoria física. Cuando el procesador necesita una dirección de memoria, la busca primero en la TLB, si no la encuentra (se produce un fallo de TLB) la busca en la TSB, pero el buscar un dato en la TSB, presenta el inconveniente que obliga al procesador a acceder memoria física, con el consiguiente coste en tiempo.
Con lo que hemos visto anteriormente podemos decir que debemos intentar que se produzcan pocos fallos de TLB, para conseguir este objetivo, tenemos 2 soluciones:
Aumenta el tamaño de la TLB, de esta forma reducimos los fallos provocados porque la dirección de memoria no esté en la TLB
Aumentar la cantidad de memoria direccionable desde la TLB.
La primera opción es inviable, en tanto que las caches TLB, son elementos físicos, nuestra única opción es aumentar la cantidad de memoria direccionable por las TLBs, para ello solo tenemos que aumentar el tamaño de la página.
MPSS
Solaris dispone de soporte para trabajar con páginas de distintos tamaños, esto nos permitirá el que nuestros procesos, en aquellas zonas de memorias, que consideremos, el acceso a ellas, provoca un incremento de los fallos de TLB, poder aumentar el tamaño de página de esas zonas, para intentar reducir el número de fallos en la TLB. Tal como hemos hecho en otros artículos vamos a ver como podemos trabajar tanto en Solaris 9 como en Solaris 10, ya que existen algunas diferencias.
MPSS en Solaris 9
Tenemos dos posibilidades para utiliza el soporte de MPSS, una de ellas es el comando ppgsz y la otra sería utilizar la librería libmpss.so. Primero veremos como cambiar el tamaño de página mediante el comando ppgsz.
Vamos a lanzar un proceso, que lo único que hace es reservar memoria y con el comando pmap veremos el tamaño de página utilizado en cada uno de los segmentos.
(root@camtrunet)# pmap -s 12310
12310: ./ejemplo2
Address Kbytes Pgsz Mode Mapped File
00010000 8K 8K r-x-- /export/home/jjmora/PROC/ejemplo2
00020000 8K 8K rwx-- /export/home/jjmora/PROC/ejemplo2
00022000 48K 8K rwx-- [ heap ]
0002E000 8K - rwx-- [ heap ]
00030000 64K 8K rwx-- [ heap ]
00040000 8K - rwx-- [ heap ]
FF280000 136K 8K r-x-- /usr/lib/libc.so.1
FF2A2000 128K - r-x-- /usr/lib/libc.so.1
FF2C2000 128K 8K r-x-- /usr/lib/libc.so.1
FF2E2000 136K - r-x-- /usr/lib/libc.so.1
FF304000 160K 8K r-x-- /usr/lib/libc.so.1
FF33C000 32K 8K rwx-- /usr/lib/libc.so.1
FF380000 8K 8K rwx-- [ anon ]
FF390000 8K 8K r-x-- /usr/platform/sun4u-us3/lib/libc_psr.so.1
FF39A000 8K 8K rwx-- /usr/lib/libdl.so.1
FF3A0000 8K 8K r--s- dev:273,0 ino:95470
FF3B0000 184K 8K r-x-- /usr/lib/ld.so.1
FF3EE000 8K 8K rwx-- /usr/lib/ld.so.1
FF3F0000 8K 8K rwx-- /usr/lib/ld.so.1
FFBFC000 16K 8K rwx-- [ stack ]
total 1440K
(root@camtrunet)#
Como podemos ver en la salida anterior del comando pmap, la tercera columna nos indica el tamaño de página utilizado en cada uno de los segmentos, en el ejemplo podemos ver que todos los segmentos utilizan una tamaño de página de 8K
Ejecutamos el mismo programa, pero utilizando el comando ppgsz, no vamos a explicar las distintas opciones de este comando, solo que vamos a utilizar -o heap=64K, con lo que indicaremos que el tamaño de página para el segmento heap, sea de 64K.
(root@camtrunet)#
(root@camtrunet)# ppgsz -o heap=64K ./ejemplo2 > /dev/null &
[3] 12918
(root@camtrunet)# ps -ef | grep ejemplo2
root 13221 9930 0 18:25:14 pts/2 0:00 grep ejemplo2
root 12918 9930 0 18:25:04 pts/2 0:00 ppgsz -o heap=64K ./ejemplo2
root 12919 12918 0 18:25:04 pts/2 0:00 ./ejemplo2
(root@camtrunet)# pmap -s 12919
12919: ./ejemplo2
Address Kbytes Pgsz Mode Mapped File
00010000 8K 8K r-x-- /export/home/jjmora/PROC/ejemplo2
00020000 8K 8K rwx-- /export/home/jjmora/PROC/ejemplo2
00022000 48K 8K rwx-- [ heap ]
0002E000 8K - rwx-- [ heap ]
00030000 1344K 64K rwx-- [ heap ]
FF280000 136K 8K r-x-- /usr/lib/libc.so.1
FF2A2000 128K - r-x-- /usr/lib/libc.so.1
FF2C2000 128K 8K r-x-- /usr/lib/libc.so.1
FF2E2000 136K - r-x-- /usr/lib/libc.so.1
FF304000 160K 8K r-x-- /usr/lib/libc.so.1
FF33C000 32K 8K rwx-- /usr/lib/libc.so.1
FF380000 8K 8K rwx-- [ anon ]
FF390000 8K 8K r-x-- /usr/platform/sun4u-us3/lib/libc_psr.so.1
FF39A000 8K 8K rwx-- /usr/lib/libdl.so.1
FF3A0000 8K 8K r--s- dev:273,0 ino:95470
FF3B0000 184K 8K r-x-- /usr/lib/ld.so.1
FF3EE000 8K 8K rwx-- /usr/lib/ld.so.1
FF3F0000 8K 8K rwx-- /usr/lib/ld.so.1
FFBFC000 16K 8K rwx-- [ stack ]
total 2384K
(root@camtrunet)#
una vez lanzado el ejecutable, utilizando el comando ppgsz, podemos ver en la salida del comando pmap, que el tamaño de la páginas utilizadas en el heap es de 64K.
Como hemos dicho, otra forma de utilizar el soporte MPSS en Solaris 9, es mediante la utilización de una librería compartida libmpss.so.1. Para utilizar esta librería, añadiremos la siguiente entrada a la variable de entorno LD_PRELOAD, en el man mpss.so.1 tenemos toda la información sobre las distintas variables de entorno que acepta la librería, para nuestro ejemplo, solo nos interesa MPSSHEAP, que nos permite definir el tamaño de página del heap.
(root@camtrunet)#
(root@camtrunet)# export LD_PRELOAD=$LD_PRELOAD:mpss.so.1
(root@camtrunet)# export MPSSHEAP=64k
(root@camtrunet)# ./ejemplo2 > /dev/null &
[1] 14729
(root@camtrunet)# pmap -s 14729
14729: ./ejemplo2
Address Kbytes Pgsz Mode Mapped File
00010000 8K 8K r-x– /export/home/jjmora/PROC/ejemplo2
00020000 8K 8K rwx– /export/home/jjmora/PROC/ejemplo2
00022000 48K 8K rwx– [ heap ]
0002E000 8K – rwx– [ heap ]
00030000 640K 64K rwx– [ heap ]
FF270000 8K 8K rwx– [ anon ]
FF280000 136K 8K r-x– /usr/lib/libc.so.1
FF2A2000 104K – r-x– /usr/lib/libc.so.1
FF2BC000 152K 8K r-x– /usr/lib/libc.so.1
FF2E2000 136K – r-x– /usr/lib/libc.so.1
FF304000 160K 8K r-x– /usr/lib/libc.so.1
FF33C000 32K 8K rwx– /usr/lib/libc.so.1
FF350000 24K 8K r-x– /usr/lib/libgen.so.1
FF366000 8K 8K rwx– /usr/lib/libgen.so.1
FF370000 8K 8K r-x– /usr/lib/mpss.so.1
FF382000 8K 8K rwx– /usr/lib/mpss.so.1
FF390000 8K 8K r-x– /usr/platform/sun4u-us3/lib/libc_psr.so.1
FF39A000 8K 8K rwx– /usr/lib/libdl.so.1
FF3A0000 8K 8K r–s- dev:273,0 ino:95470
FF3B0000 184K 8K r-x– /usr/lib/ld.so.1
FF3EE000 8K 8K rwx– /usr/lib/ld.so.1
FF3F0000 8K 8K rwx– /usr/lib/ld.so.1
FFBFC000 16K 8K rwx– [ stack ]
total 1728K
(root@camtrunet)#
Podemos ver en la salida del comando pmap, que el tamaño de página del heap es de 64K.
MPSS en Solaris 10
En Solaris 10, podemos utilizar perfectamente cualquiera de los métodos empleados en Solaris 9, utilizando exactamente igual, tanto el comando ppgsz, como la librería compartida libmpss.so.1. Pero existe una diferencia frente a Solaris 9 y es que Solaris 10 tiene activado el soporte MPSS por defecto, si ejecutamos un comando, por ejemplo sleep.
root@t1000 # sleep 100 &
[2] 745
root@t1000 # pmap -s 745
745: sleep 100
Address Bytes Pgsz Mode Mapped File
00010000 8K 8K r-x-- /usr/bin/sleep
00022000 8K 8K rwx-- /usr/bin/sleep
00024000 8K 8K rwx-- [ heap ]
00026000 40K - rwx-- [ heap ]
FF000000 64K 64K r-x-- /usr/lib/locale/es_ES.UTF-8/es_ES.UTF-8.so.3
FF010000 192K - r-x-- /usr/lib/locale/es_ES.UTF-8/es_ES.UTF-8.so.3
FF040000 64K 64K r-x-- /usr/lib/locale/es_ES.UTF-8/es_ES.UTF-8.so.3
FF050000 1792K - r-x-- /usr/lib/locale/es_ES.UTF-8/es_ES.UTF-8.so.3
FF210000 64K 64K r-x-- /usr/lib/locale/es_ES.UTF-8/es_ES.UTF-8.so.3
FF22E000 16K 8K rwx-- /usr/lib/locale/es_ES.UTF-8/es_ES.UTF-8.so.3
FF240000 64K 64K r-x-- /usr/lib/locale/common/methods_unicode.so.3
FF25E000 8K 8K rwx-- /usr/lib/locale/common/methods_unicode.so.3
FF280000 448K 64K r-x-- /lib/libc.so.1
FF2F0000 64K - r-x-- /lib/libc.so.1
FF300000 64K 64K r-x-- /lib/libc.so.1
FF310000 64K - r-x-- /lib/libc.so.1
FF320000 192K 64K r-x-- /lib/libc.so.1
FF350000 32K 8K r-x-- /lib/libc.so.1
FF368000 32K 8K rwx-- /lib/libc.so.1
FF370000 8K 8K rwx-- /lib/libc.so.1
FF380000 8K 8K rwx-- [ anon ]
FF390000 8K 8K r-x-- /platform/sun4v/lib/libc_psr.so.1
FF3A0000 16K 8K rwx-- [ anon ]
FF3A4000 8K - rwx-- [ anon ]
FF3B0000 192K 64K r-x-- /lib/ld.so.1
FF3E0000 16K 8K r-x-- /lib/ld.so.1
FF3F4000 8K 8K rwx-- /lib/ld.so.1
FF3F6000 8K 8K rwx-- /lib/ld.so.1
FFBF0000 64K 64K rw--- [ stack ]
total 3560K
Podemos ver en la salida del comando pmap que algunos de los segmentos tienen un tamaño de página de 64K. Podemos comprobar que el soporte MPSS está activado chequeando los siguientes parámetros de kernel con mdb.
root@t1000 # mdb -k
Loading modules: [ unix krtld genunix specfs dtrace ufs sd px ip sctp usba fctl nca lofs zfs
random crypto nfs ptm ]
> exec_lpg_disable/D
exec_lpg_disable:
exec_lpg_disable: 0
>
> use_brk_lpg/D
use_brk_lpg:
use_brk_lpg: 1
>
> use_stk_lpg/D
use_stk_lpg:
use_stk_lpg: 1

>
> use_zmap_lpg/D
use_zmap_lpg:
use_zmap_lpg: 1
>
> use_text_pgsz4m/D
use_text_pgsz4m:
use_text_pgsz4m:1
>
> use_text_pgsz64k/D
use_text_pgsz64k:
use_text_pgsz64k: 1
>
> use_initdata_pgsz64k/D
use_initdata_pgsz64k:
use_initdata_pgsz64k: 1
>
Una breve descripción de para qué se pueden utilizar cada uno de los parámetro:
exec_lpg_disable, habilita/deshablita el soporte para páginas grandes.
use_brk_lpg, permite páginas grandes en el heap.
use_stk_lpg, permite páginas grandes en el stack.
use_zmap_lpg, permite páginas grandes anon.
use_text_pgsz4m, permite páginas de 4m en el segmento de texto.
use_text_pgsz64k, permite páginas de 64K en el segmento de texto.
use_initdata_pgsz64k, permite páginas de 64K en el segmento de datos.
Estos parámetros podemos, o bien definirlos en el ficheros /etc/system, para que se tomen en cuenta a la hora de arrancar el sistema, o podemos modificarlos en caliente con mdb, vamos a ejecutar el programa ejemplo2, para ver el tamaño de página de los distintos segmentos.
root@t1000 # ./ejemplo2 > /dev/null &
[4] 786
root@t1000 # pmap -s 786
786: ./ejemplo2
Address Bytes Pgsz Mode Mapped File
00010000 8K 8K r-x-- /export/home/jjmora/ejemplo2
00020000 8K 8K rwx-- /export/home/jjmora/ejemplo2
00022000 48K 8K rwx-- [ heap ]
0002E000 8K - rwx-- [ heap ]
00030000 384K 64K rwx-- [ heap ]
FF280000 192K 64K r-x-- /lib/libc.so.1
FF2B0000 64K - r-x-- /lib/libc.so.1
FF2C0000 192K 64K r-x-- /lib/libc.so.1
FF2F0000 128K - r-x-- /lib/libc.so.1
FF310000 256K 64K r-x-- /lib/libc.so.1
FF350000 24K - r-x-- /lib/libc.so.1
FF356000 8K 8K r-x-- /lib/libc.so.1
FF368000 32K 8K rwx-- /lib/libc.so.1
FF370000 8K 8K rwx-- /lib/libc.so.1
FF380000 8K 8K rwx-- [ anon ]
FF390000 8K 8K r-x-- /platform/sun4v/lib/libc_psr.so.1
FF3A0000 16K 8K rwx-- [ anon ]
FF3A4000 8K - rwx-- [ anon ]
FF3B0000 192K 64K r-x-- /lib/ld.so.1
FF3E0000 16K 8K r-x-- /lib/ld.so.1
FF3F4000 8K 8K rwx-- /lib/ld.so.1
FF3F6000 8K 8K rwx-- /lib/ld.so.1
FFBF0000 64K 64K rwx-- [ stack ]
total 1688K
root@t1000 #
De la salida del comando pmap podemos ver que algunos de los segmentos, tienen un tamaño de página de 64K, ahora con mdb, vamos a cambiar el caliente los parámetros del kernel.
root@t1000 # mdb -kw
Loading modules: [ unix krtld genunix specfs dtrace ufs sd px ip sctp usba fctl nca lofs zfs random crypto nfs ptm ]
> exec_lpg_disable/W 1
exec_lpg_disable: 0x1 = 0x1
> use_brk_lpg/W 0
use_brk_lpg: 0x1 = 0x0
> use_stk_lpg/W 0
use_stk_lpg: 0 = 0x0
>
> $q

root@t1000 # ./ejemplo2 > /dev/null &
[1] 800
root@t1000 # pmap -s 800
800: ./ejemplo2
Address Bytes Pgsz Mode Mapped File
00010000 8K 8K r-x-- /export/home/jjmora/ejemplo2
00020000 8K 8K rwx-- /export/home/jjmora/ejemplo2
00022000 48K 8K rwx-- [ heap ]
0002E000 8K - rwx-- [ heap ]
00030000 64K 8K rwx-- [ heap ]
00040000 8K - rwx-- [ heap ]
00042000 56K 8K rwx-- [ heap ]
00050000 8K - rwx-- [ heap ]
00052000 56K 8K rwx-- [ heap ]
00060000 8K - rwx-- [ heap ]
00062000 56K 8K rwx-- [ heap ]
00070000 8K - rwx-- [ heap ]
00072000 8K 8K rwx-- [ heap ]
FF280000 192K 64K r-x-- /lib/libc.so.1
FF2B0000 64K - r-x-- /lib/libc.so.1
FF2C0000 192K 64K r-x-- /lib/libc.so.1
FF2F0000 128K - r-x-- /lib/libc.so.1
FF310000 256K 64K r-x-- /lib/libc.so.1
FF350000 24K - r-x-- /lib/libc.so.1
FF356000 8K 8K r-x-- /lib/libc.so.1
FF368000 32K 8K rwx-- /lib/libc.so.1
FF370000 8K 8K rwx-- /lib/libc.so.1
FF380000 8K 8K rwx-- [ anon ]
FF390000 8K 8K r-x-- /platform/sun4v/lib/libc_psr.so.1
FF3A0000 16K 8K rwx-- [ anon ]
FF3A4000 8K - rwx-- [ anon ]
FF3B0000 192K 64K r-x-- /lib/ld.so.1
FF3E0000 16K 8K r-x-- /lib/ld.so.1
FF3F4000 8K 8K rwx-- /lib/ld.so.1
FF3F6000 8K 8K rwx-- /lib/ld.so.1
FFBFE000 8K 8K rwx-- [ stack ]
total 1520K
root@t1000 #
Una vez cambiado los parámetros, al ejecutar el programa ejemplo2, con el comando pmap podemos ver que el tamaño de página de los segmentos heap y stack es de 8K.
Pruebas
La prueba que vamos a realizar en provocar fallos en las TLBs para ver como el número de fallos varía al cambiar el tamaño de página. Todos los ejemplos del artículo los hemos estado realizando con el siguiente programa en C ejemplo2.c, el cual reserva 50 bloques de 64K, los escribe, vuelve a escribir en cada bloque de 64K otras 100 veces y por último libera toda la memoria.
#include < stdio.h >

main()
{

char *p[100];
char *c;
int n,j,k;

for(n=0;n< 50;n++)
{
p[n]=malloc(65500);
c=p[n];
printf("n malloc(64k) pages: %d t dir.page: 0x%lx",n*8,c);
for(j=0;j< 65500;j++)
{
c++;
*c='a';
}
sleep(1);
}

sleep(2);
for(k=0;k< 100;k++)
{
for(n=0;n< 50;n++)
{
c=p[n];
for(j=0;j< 65500;j++)
{
c++;
*c='a';
}
}
}

for(n=0;n< 50;n++)
{
free(p[n]);
printf("n free(64k)");
}
return;
}
Vamos a realizar una pequeña prueba de carga, consistente en lanzar unos 100 procesos en background utilizando el programa ejemplo2, lo que provocará no solo un consumo de memoria, sino que los distintos procesos comenzarán a solicitar páginas de memoria en las que estarán escribiendo, esto provocará una gran cantidad de fallos en las TLBs.
Hemos realizado la prueba 2 veces, la primera, utilizando un tamaño de página de heap de 8K y en la segunda prueba, hemos modificado el tamaño de página de heap a 64K. En la siguiente gráfica hemos representado a lo largo del tiempo de duración de la prueba, cuantos fallos de TLB se han producido.

La gráfica explica de forma tajante, como el mismo programa, provoca distintos resultados sobre las TLBs, variando el tamaño de página.
Conclusión
Hemos visto en qué consiste MPSS, cómo podemos utilizar el soporte de MPSS, cómo podemos modificar parámetros del kernel y cómo podemos aumentar el rendimiento de nuestro sistema cambiando el tamaño de página, pero no podemos aplicar lo que hemos visto a todos nuestros problemas de acceso a la memoria, cada posible problema debe ser estudiado por separado, ya que, el que un proceso funcione mejor con un tamaño de página de 64K, no significa que a todos los procesos les pueda ocurrir lo mismo, ya que este tamaño de página puede suponer para otro proceso, que se consuma demasiada memoria.

Comentarios

Entradas populares de este blog

MikroTik QoS Script generator

Comandos Solaris

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