Comprobando el rendimiento de los sistemas de archivos de Linux

Uno de los problema mas comunes al instalar Linux o administrar una maquina en Linux es encontrar el sistema de archivos que mas se ajuste a nuestras necesidades. a continuación tenemos un script que creara varios sistemas de archivos y realizara diversas pruebas.

echo "****************************"
echo "*Creating Files for loopback*"
echo "****************************"
echo " "
echo "Creating file"
dd if=/dev/zero of=filesystem.fs count=5000000
echo "Formating btrfs filesystem"
mkfs.btrfs filesystem.fs
mkdir filesystem
echo "Configuring loopback"
losetup /dev/loop0 filesystem.fs
mount -t btrfs /dev/loop0 filesystem
cd filesystem
cp ../script.sh ./
sh script.sh
umount /dev/loop0
echo "Formating ext2 filesystem"
mkfs -t ext2 /dev/loop0
mount -t ext2 /dev/loop0 filesystem
cd filesystem
cp ../script.sh ./
sh script.sh
echo "Formating ext3 filesystem"
mkfs -t ext3 /dev/loop0
mount -t ext3 /dev/loop0 filesystem
cd filesystem
cp ../script.sh ./
sh script.sh
echo "Formating ext4 filesystem"
mkfs -t ext4 /dev/loop0
mount -t ext4 /dev/loop0 filesystem
cd filesystem
cp ../script.sh ./
sh script.sh
echo "Formating XFS filesystem"
mkfs -t xfs -f /dev/loop0
mount -t xfs /dev/loop0 filesystem
cd filesystem
cp ../script.sh ./
sh script.sh
echo ""Formating ReiserFS filesystem"
mkfs.reiserfs /dev/loop0
mount -t reiserfs /dev/loop0 filesystem
cd filesystem
cp ../script.sh ./
sh script.sh

creara un archivo de 5gb que utilizara como disco duro (enlazado con /dev/loop0) para ejecutar la siguiente batería de pruebas.

Prueba 1: Crear 10000 ficheros


mkdir fileDir
echo "Creating Files"
date --rfc-3339=ns
for i in {1..10000}
do
touch fileDir/$i
done
date --rfc-3339=ns

create 10000 filesen esta prueba XFS se lleva la peor parte en esta parte posiblemente a causa de las write barriers que lo obliga a escribir en el disco mas veces de las necesarias, encfs a pesar de estar encriptado no se ve penalizado. Los otros sistemas de archivos tienen un rendimiento similar.

Prueba 2: Buscar dentro de los 10000 ficheros

echo "Finding Fluzo generator"
 date --rfc-3339=ns
 for i in {1..10000}
 do
 grep "fluzo"  fileDir/$i
 done
 date --rfc-3339=ns

find a file

Esta prueba obliga al sistema abrir todos los archivos para buscar en su contenido la palabra “fluzo”, el retraso de encfs se debe por su penalización al estar encriptado el resto de sistemas de archivo no tienen una diferencia de rendimiento destacable siendo XFS y btrfs un poco mas lentos que la familia ext y reiserFS

Prueba 3: Eliminar los archivos de la prueba 1

echo "Removing File Directory"
 date --rfc-3339=ns
 rm -rf fileDir
 date --rfc-3339=ns

Remove file directory

XFS se lleva la peor parte en esta prueba siendo extremadamente lento (¿write barriers?) , encfs también sufre una pequeña penalización  pero no es tan destacable como el XFS  los sistemas de archivos que utilizan un árbol como estructura sufren una mayor penalización que los que usan una lista.

Prueba 4: Buscar en una estructura de 10000 directorios

mkdir dirDir
 echo "Creating Directories"
 cd dirDir
 for i in {1..10000}
 do
 mkdir $i
 done
 cd ..
 echo "Finding Fluzo into directory struct"
 date --rfc-3339=ns
 find fluzo
 date --rfc-3339=ns

find a file into directory struct

Esta prueba obliga al kernel a abrir el contenido de todos los directorios y buscar en sus estructuras. Los arboles B+ de ReiserFs le otorgan una alta ventaja  frente al resto siendo el mas eficiente el encfs el perdedor es ext3

Prueba 5: Eliminar 10000 directorios

echo "Removing Directories"
 date --rfc-3339=ns
 rm -rf dirDir
 date --rfc-3339=ns

remove 10000 directories

La operaciones de eliminación de ficheros son mas rápidas en ext2 sobre el resto de los ficheros, los otros sistemas se ven penalizados al balancear los arboles el sistema encfs sufre una altisima penalización

Prueba 6: Copiar el kernel desde el directorio home del usuario

echo "Copying Kernel from user home"
 date --rfc-3339=ns
 cp ~/kernel.tar.gz ./
 date --rfc-3339=ns

copy kernel from user home

La copia de un archivo grande muestra que tanto ext3 como ext2 no pueden alardear de un gran rendimiento el resto de sistemas de archivos realizan la misma acción en menos tiempo (casi la mitad) a excepción de Encfs que queda en un puesto intermedio

Prueba 6: Copiar el kernel al home del usuario

echo "Copying Kernel to user home"
 date --rfc-3339=ns
 cp kernel.tar.gz ~/kernel2.tar.gz
 date --rfc-3339=ns
 rm -rf ~/kernel2.tar.gz

copy kernel to user home

La siguiente prueba obliga al sistema a realizar una lectura del sistema del archivo copiado en el paso anterior XFS aparece como el vencedor en cuanto a rendimiento seguido de ext2 y btrfs el resto de sistemas de archivos muestran un retraso considerable al realizar la lectura siendo el mas lento ext3

Prueba 7 : Descomprimir el tarball del kernel

echo "Untar Kernel source"
 date --rfc-3339=ns
 tar -xzf kernel.tar.gz
 date --rfc-3339=ns

untar kernel

La descompresión del tarball del kernel obliga al sistema de archivo a crear un gran numero de directorios y sus archivos XFS vuelve a ser el mas lento, y ext2 el mas rapido,  ext3,ext4  y reiserFS se aproximan al rendimiento de ext2 quedando btrfs un poco descolgado y encfs en un punto intermedio

Prueba 8: Comprimir el codigo fuente del kernel

echo "tar Kernel source"
 date --rfc-3339=ns
 tar -czf paquete.tar.gz linux-2.6.32/
 date --rfc-3339=ns

tar kernel source

Esta prueba combina la lectura de todos los archivos del kernel con la creación de un nuevo archivo en el caso de XFS su velocidad de lectura compensa su lentitud de escritura teniendo todos los sistemas un rendimiento simila a excepciçon de encfs que se vuelve a mostrar como el mas lento de todos

Prueba 9: Eliminar la carpeta que contiene el codigo del kernel

echo "Removing Kernel Source Tree"
 date --rfc-3339=ns
 rm -rf linux-2.6.32
 date --rfc-3339=ns

remove kernel source tree

Esta prueba obliga al sistema de archivos a eliminar un gran conjunto de archivos y directorios. ext4 es el mas rapido con diferencia y XFS se perfila como el sistema mas lento de todos los demas sistemas de archivos muestran un rendimiento un poco mas lento que ext4 (entre 2 y 4 veces mas lentos).

Prueba 10: Copiar 10 veces el tarball del kernel

echo "Copying 10 times kernel tarball"
 date --rfc-3339=ns
 for i in {1..10}
 do
 cp paquete.tar.gz paquete.tar.gz.$i
 done
 date --rfc-3339=ns

copy ten times kernel tarball

En esta prueba obligamos al sistema de archivos a crear 10 archivos de tamaño mediano leyendo un archivo localizado en el mismo sistema de archivos, reiserFS es el mas rapido aunque pero no llefa a ser doblado por ninguno de los otros sistemas de archivos a excepción de Encfs.

Prueba 11: Crear un archivo de 1Gb

echo "Creating a 1GB file"
 date --rfc-3339=ns
 dd if=/dev/zero of=gigafile.file count=2097152
 date --rfc-3339=ns

create 1GB file

En esta prueba llenamos 1/5 parte del sistema de ficheros con un unico archivo siendo los rendimientos similares ganando XFS y el resto de los sistemas teniendo unos retrasos no mayores que el doble en el caso de ReiserFS y Ext3, encfs sigue demostrando que es el mas lento con diferencia (4 veces mas lento aprox).

Prueba 12: Copiar el archivo de 1Gb creado en la prueba anterior

echo "Copying gigafile"
 date --rfc-3339=ns
 cp gigafile.file newgigafile.file
 date --rfc-3339=ns

copy a gigabite file

En esta prueba obligamos al sistema de archivos a leer 1/5 parte de su contenido y a escribirla en otro archivo btrfs es el mas rapido con diferencia seguido de ext4 y a lo lejos por XFS tanto ext2, ext3 y reiser son los menos apropiados para trabajar con archivos tan grandes. encfs tiene un rendimiento lento quedando en un punto intermedio, posiblemente al usarse sobre un sistema ext4.

Prueba 13: Separar un archivo en varias partes de distintos tamaños

dd if=/dev/zero of=tosplit.img count=20480
 mkdir splitDir
 echo "Spliting 10 Mb file"
 date --rfc-3339=ns
 split -b 1000 -d tosplit.img splitDir/mi_split1000.s
 split -b 1024 -d tosplit.img splitDir/mi_split1024.s
 split -b 2048 -d tosplit.img splitDir/mi_split2048.s
 split -b 4096 -d tosplit.img splitDir/mi_split4096.s
 split -b 8192 -d tosplit.img splitDir/mi_split8192.s
 date --rfc-3339=ns

split 10 MB file

En esta prueba se obliga al sistema a duvidir un fichero de 10Mb en  un conjunto de partes del tamalo de 1000,1024,2048,4096 y 8192 bytes, la lentitud de XFS creando archivos lo relega a ser el mas lento de todos reiserFS y ext2 quedan en un segundo grupo que tarda un poco mas del doble que el grupo de cabeza formado por btrfs,ext3 y ext4. Encfs tarda un poco mas del triple.

Prueba 14: Leer el archivo de 1Gb con cat redirigiendo la salida a /dev/null

echo "Cat 1GB file to dev/null"
 date --rfc-3339=ns
 cat gigafile.file >/dev/null
 date --rfc-3339=ns

cat 1gb file to /dev/null

En la ultima prueba obligamos al sistema de archivos a leer un archivo grande de 1Gb 1/5 el tamaño total. reiserFS muestra un rendimiento sorprendente siendo el sistema mas recomendado para lecturas de archivos grandes, seguido de btrfs con un tiempo de unas 10-15 veces mas lento mientras que el resto de sistemas de archivos quedan agrupados en unos tiempos de mas del doble que btrfs.

Podemos observar todas las pruebas juntas en la siguientes listas

All Filesystem testy los tiempos totales de todas las operaciones juntas

total time filesystempara un uso mixto los mas recomendables son btrfs y ext4 seguidos de reiser y ext2 junto a ext3 encfs se perfila como uno de los mas lentos y menos recomendable.

podiamos clasificar los 3 mejores en la siguiente lista:

  • creación ficheros:     reiserFS, ext4
  • eliminación ficheros: reiserFS, ext4
  • lectura de ficheros: ext2, btrfs,xfs (grandes)

Las siguientes pruebas no han tenido en cuenta características como journaling, capacidades de encriptación, rendimiento en configuraciones RAID y sistemas de archivos con mucho movimiento donde aparezcan penalizaciones por fragmentación.

Practica 3 ASO

1. (Kernel Lista) Modifica la cabecera “include/linux/list.h” del kernel para que se pueda

utilizar en un programa de usuario. Escribe un programa que utiliza las funciones de listas enlazadas dentro de list.h para:

  • insertar números aleatorios.
  • recorrer la lista imprimiendo los valores.
  • eliminar un nodo de la lista (utilizando un menú de selección).

2. (Simulador de Procesos) En este problema, queremos estudiar el efecto de diferentes
parámetros relacionado con “timeslice” a diferentes algoritmos de planificación. Escribe un
programa que es simula el comportamiento del gestor de planificación con diferentes valores
de timeslice y para diferentes cargas de cambio de contexto de procesos. Para ello debe
seguir los siguientes pasos:
a) Utilizando su libraría de funciones que ha creado en practicas anteriores, genera los
siguientes datos para procesos que van a querer utilizar el CPU: (i) el PID de cada
procesos, un número aleatorios que representa el tiempo de llegada, y un numero
aleatorio que representa el tiempo total que va a ejecutar en el CPU. Como ejemplo,
los números puede ser como las siguientes:
PID T_llegada            T_total
1        30      0.783560
2         54     17.282004
3         97     32.814522
Que significa que el primer proceso llega a 30 segundos y quiere 0.783560 segundos para ejecutar, el segundo proceso llega a t=54 segundos y necesita 17.28 segundos para ejecutar, ect.
b) Escribe un función que dado los valores generado, es capaz de contar el timeslice y
utilizar planificación de “preemption”, si necesario, para intercambiar procesos cuando
el timeslice de un proceso se agota.
c) Escribe un algoritmo de político “Round Robin”.
d) Escribe un función que calcula el tiempo media de espera y el tiempo media de la tasa de terminar para todos los procesos.
e) Ejecuta su simulación para tiempos de sobrecargo de cambio de contexto de 0, 5, 10, 15, 20 y 25 milisegundos; y por valores de timeslice de: 50, 100, 250, y 500
milisegundos. Hacer gráficos (plots) de lo que encuentras.

Lo primero que tenemos que hacer es copiar la libreria list.h a un archivo nuevo (el kernel de linux no esta en C estándar) y compilar para ver si lo acepta.

Al primer intento nos va a decir que no por las librerías así que tendremos que borrarlas del código

//#include <linux/poison.h>
//#include <linux/prefetch.h>
//#include <asm/system.h>

Volvemos a compilar y tenemos problemas con las variables LIST_POISON esto se produce en las siguientes dos funciones:

  • funcion1

static inline void list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
//entry->next = LIST_POISON1;
//entry->prev = LIST_POISON2;
}

  • funcion2

static inline void hlist_del(struct hlist_node *n)
{
__hlist_del(n);
//n->next = LIST_POISON1;
//n->pprev = LIST_POISON2;
}

Así  ya nos compila el código.

El uso de las listas del kernel difiere del uso clásico que tenemos en mente.

Diagrama clasico de la estructura de una lista
Diagrama clásico de la estructura de una lista

aquí tenemos el esquema de uso de una kernel list

comportamiento de una kernel list
comportamiento de una kernel list

la principal diferencia es que los enlaces de la lista son una estructura dentro de nuestra estructura, por lo tanto no nos movemos a través de nuestra lista sino que vamos accediendo a partes internas de la lista. El principal problema de utilizar este método es acceder a la estructura que contiene los enlaces, esto se realiza mediante el calculo del offset de un puntero.

/*
* getCustomListPointer — gets the customlist memory address from a listPointer calculating the offsett
* listPointer — a pointer that point a list element
*/

struct customList *getCustomListPointer(struct list_head *listPointer)//DONE
{
void * toret=NULL;
toret=listPointer;
//We use a void pointer because i can’t sobrem problems with pointers aritmetic
toret= (toret – ((unsigned long) &((struct customList *)0)->list));
return toret;
}

Toret es un puntero a  void para evitar problemas en la aritmética de punteros, si este fuera un char puntero -1 seria iguar a puntero -1(tamaño char) por lo que tendríamos problemas de fallo de segmento.

La estructura del código se puede observar en el siguiente esquema:

Organización del simulador de procesos
Organización del simulador de procesos

Existen unos pequeños bugs y características no implementadas en la aplicación

  • El iterador usa variable global que puede sufrir efectos laterales (las funciones copyCustomList tiene el problema arreglado al ser recursivas y utilizar un registro como indicador de fin de recursividad)
  • Habría que mirar el tamaño de una variable void* (si es >1 produce segmentation fault por mal calculo de offset)
  • Añadir elemento y otras funciones no esta implementado solo seria añadir una sucesión de printf y scanf en el código.
  • No esta implementado el paso de parámetros como por ejemplo establecer el numero de procesos que se simularan (cambiar 15000, por una variable en linea 113 de main.c)
Codigo Fuente
Codigo Fuente

Funcionamiento:

La ejecución muestra por pantalla los datos de cada proceso, timeslice y contextChangue según termina cada proceso,  se ha definido un conjunto de 15000 procesos para sacar estadísticas.

Los ejecución recomendada es ./ejecutable> salida.cvs siendo salida.cvs un archivo comma separated values que se importara a mysql para realizar calculos de forma mas sencilla. La definición de la tabla que se usara esta en el siguiente enlace (Database struct Definitión)

Una vez obtenida la salida la importación a MySql se realiza ejecutando un orden similar a esta.

LOAD DATA LOCAL INFILE’/home/luzem/NetBeansProjects/Practica3/dist/Debug/GNU-Linux-x86/salida.csv’
INTO TABLE roundrobin
FIELDS TERMINATED BY ‘,’
LINES TERMINATED BY ‘\n’

Con esto y un conjunto de consultas SQL podemos obtener los datos para procesar en GNUPLOT.

Gráficas

ContextChangue effects
Como Afecta el ContextChangue en la media global

En  la anterior gráfica podremos ver los efectos del incremento del contextChangue sobre el tiempo medio de espera, observando como el impacto es independiente del timeslice.

Tiempo medio de espera
Tiempo medio de espera

El tiempo medio de espera de cada proceso aumenta en función de timeslice y dada que la diferencia entre timeslice y contextchangue es muy alta es el aumento del timeslice quien castiga mas a los tiempos de espera.

En cambio si tenemos en cuenta la media del tiempo total de espera para los conjuntos de timeslice y contextchangue obtenemos la siguiente media

Impacto sobre el la media del tiempo total de espera
Impacto sobre el la media del tiempo total de espera

Cuando el timeslice es muy pequeño el numero de cambios de contexto aumenta penaliza el tiempo total de espera de una forma mas que considerable.

Si extraemos el 15% inicial y el 15% final y calculamos las medias para cada segmento obtenemos las siguiente grafica

Differences between short long process
Differences between short long process

En la anterior gráfica observamos que el impacto sobre la media de tiempo de espera, afecta mayormente a la parte central del conjunto de procesos.

Mejoras:

Utilizar varias listas round robin con diferente timeslice  y mover los elementos customList en función del contador de paradas (timeslice grande para procesos largos y timeslice pequeño para procesos cortos)

Practica 4 ASO, Fork o Threads ¿Que es mas eficiente?

Objetivo de Practica:

Vamos a empezar estudiar como el proceso es un parte fundamental para entender el funcionamiento del kernel. En este practica, vamos a compilar el kernel….

Otra conjunta de problemas que queremos ver tiene que ver con threads, como hemos visto en clase. Vamos a comprobar algunos problemas y ver como funcionan.

  1. Escribe un código para comparar el tiempo de ejecución para crear N procesos de fork y N threads. Plot su resultado como función de N.
  2. Comprobar como funciona el código de “mutexes” con threads. Intenta ejecutar este codigo sin los mutex locks. ¿Cuantos veces tiene un “race condition”. Puedes indicar como un % cuantos veces tienes un races?
  3. Compilar el kernel para que se puede compilar un kernel. Para ello tiene que instalar las utilidades de make y el libraría de “libncurses-dev”.

Parte 1

Cuando queremos aprovechar las ventajas de los nuevos procesadores de varios núcleos tenemos que recurrir a hacer fork’s del proceso o invocar threads

¿Cual es mejor?, usando el codigo  para los fork process y para los c threads (recordar añadir la opción -lpthread en gcc al compilar este ultimo)

aqui la tabla de tiempos en mi equipo

FORK FORK FORK Threads Threads Threads
real user sys real user sys
50 0.016 0.002 0.007 0.004 0.001 0.002
300 0.074 0.005 0.049 0.014 0.001 0.005
500 0.129 0.019 0.107 0.019 0.004 0.008
1000 0.238 0.023 0.226 0.037 0.000 0.017
5000 1.152 0.161 0.266 0.176 0.003 0.058
10000 2.306 0.255 2.685 0.354 0.018 0.114
30000 7.116 0.685 7.108 1.032 0.055 0.481
50000 11.665 1.048 11.665 1.764 0.114 0.693

y como interpretar datos de una tabla no es muy rapido ni intuitivo podemos crearemos unos gráficos con gnuplot, guardamos la tabla en un archivo de texto, (Fork and Threads Stats) abrimos el gnuplot y escribimos

set xlabel “Number of Process”

set ylabel “Seconds to Create”

plot “stats.txt” using 1:2 w lines title ‘Fork Real’,”stats.txt” using 1:3 w lines title “Fork User”,”stats.txt” using 1:4 w lines title ‘Fork Sys’,”stats.txt” using 1:5 w lines title ‘Threads Real’,”stats.txt” using 1:6 w lines title ‘Threads User’,”stats.txt” using 1:7 w lines title ‘Threads Sys’

y aparece en nuestra pantalla el siguiente gráfico

threads_vs_fork_total_times
threads_vs_fork_total_times

puede dar lugar a confusión así que lo sacaremos solo con el tiempo real

total_time_fork_vs_threads
total_time_fork_vs_threads

en definitiva si quieres ganar rendimiento el uso de los threads es mas adecuado.

Parte 2

Threads con Mutex
Threads con Mutex

Threads sin Mutex
Threads sin Mutex

Tras ejecutar los threads sin mutex varias veces el contador solo llega a valores entre 110-130 lo que significa que el 50-60% de los threads entran en “race condicion”

Parte 3

Documentación detallada para compilar el nucleo (Gentoo Handbook)

Librerias estaticas y Librerias dinamicas

Cunado estas compilando tus fuentes en Linux una de las opciones son el tipo de enlace que quieres realizar en tus librerías estático o dinámico ¿en que se diferencian?

  • Librerías estáticas (lib_nombrelibreria.a)

Una librería estática se “encastra” dentro de nuestro ejecutable lo que significa que podemos llevarlo a otro ordenador sin temer a que nos falten librerías.

pero si las librerías tienen un bug y aparece una versión que arregla ese fallo tienes que recompilar el código

son mas grandes al llevar las librerías encastradas

son mas rápidos en la ejecución porque las funciones están dentro del ejecutable no  tenemos que buscarlas

  • Librerías dinámicas (lib_nombre_libreria.so)

Una librería dinámica no se “encastra” dentro de nuestro ejecutable por lo que nuestro ejecutable sera mas pequeño pero

Si nos llevamos nuestro ejecutable a otra maquina las librerías tienes que ir con el

La ejecución es mas lenta a causa de tener que ir a buscar la librería fuera del ejecutable

Si existe un bug en la librería se actualiza y arreglado en todos los ejecutables que la usan , si es un cambio en una función (mas parámetros,cambio de comportamiento ..) tenemos que volver a compilar todo.

¿Como se compilan?

Estaticas

  1. Crear los ficheros objero (.o) a partir de nuestro codigo fuente (.c)

    gcc -c fuente.c -o fuente.o

  2. Crear las librerías (.a)

    ar -rv libnombre.a fuente1.o fuente2.o … fuenten.o

    librerias_estaticas
    librerias_estaticas

Dinámicas

  1. Crear los ficheros objeto

    gcc -c fuente.c -o fuente.o

  2. Crear las librerias dinamicas

    ld -o liblibreria.so objeto1.o objeto2.o … -shared

    librerias_dinamicas
    librerias_dinamicas

Listas simplemente enlazadas

Escribir un programa en C que define una estructura de una listas enlazada single.
Este lista enlazada debe contener un campo entero, denominada “pid”,
y también un campo de tipo array estatica denominada mem[], representando algo no especificada.
A continuación, escribe funciones que van a realizarlas siguientes operaciones fundamentales con este estructura:

  • crear un elemento que inicializa cada uno de los campos
  • insertar un nuevo elemento al principio (a la cabecera) de la lista
  • insertar un nuevo elemento al final (a la cola) de la lista
  • un iterador que es capaz de imprimir cada uno de los campos de cada elemento y también imprime cada uno de los elementos del array estática, mem[], correspondiente.
  • Eliminar un elemento de la lista a través del valor del pid

No es la solución mas elegante pero mi C se estaba oxidando demasiado

entrega_1_ASO