diff --git a/tp5/tp5.md b/tp5/tp5.md new file mode 100644 index 0000000000000000000000000000000000000000..78515c8e3e8c52e7a37a52b4c8dc070561519bbd --- /dev/null +++ b/tp5/tp5.md @@ -0,0 +1,303 @@ +# Introduction + +## Appels systèmes et fonctions utiles + +Deux appels systèmes permettent de manipuler directement la table des +pages d'un processus : + + - `mmap` : ajoute une ou plusieurs pages à l'espace d'adressage + virtuelle d'un processus. Les pages ajoutées peuvent être + initialisées à partir d'un fichier (p.ex. pour lire depuis/écrire + vers ce fichier), ou être anonyme (pages « vides », mises à 0). Ces + pages peuvent être marquées privées (*copy on write*), ou partagées. + Une page partagée reste commune au processus parent et ses éventuels + enfants **même en cas de modification de son contenu**. Lors de + l'appel à `mmap`, la taille de la zone doit être connue et ne peut + ensuite plus être modifiée. + - `munmap` : efface une ou plusieurs entrées de la table des pages + d'un processus. + +# Travail à réaliser + +## *Memory-mapped-ios* + +En partant du fichier suivant, comparer les performances des +entrées-sorties utilisant des appels systèmes (ici `read` et `write`), +et des entrées-sorties exploitant directement le système de mémoire +virtuelle. + +Pour cela, vous devez dans un premier temps compléter le fichier source +ci-dessous en donnant le code de la fonction `copy_with_mmap`. Cette +fonction doit copier le contenu du fichier `in.bin` vers le fichier +`out.bin`. Pour cela, elle doit créer des pages associées à ces fichiers +dans son espace de mémoire virtuel, puis utiliser la fonction standard +`memcpy` pour effectuer la copie. Enfin, les pages doivent être +supprimées de l'espace virtuel et les fichiers sources et destinations +doivent être fermées. Pour connaître la taille du fichier à copier, et +ainsi déterminer le nombre de page à créer, il est possible d'utiliser +l'appel système `fstat`. + +Une fois que la fonction est codée, vous pourrez comparez les +performances des deux approches pour différentes tailles de fichier +source. Pour allouer rapidement un fichier d'une taille donnée, vous +pouvez utiliser la commande `fallocate` comme ci-dessous (dans +l'exemple, un fichier de 16MiB est alloué). À noter qu'il faut d'abord +effacer le fichier pour que la taille demandée soit effectivement prise +en considération. + +``` bash +rm in.txt +fallocate -l $((16 * 1024 * 1024)) in.txt +``` + +Si la commande `fallocate` n'est pas disponible, vous pouvez utiliser la +commande `dd` qui est un peu plus lente : + +``` bash +dd if=/dev/urandom of=in.txt bs=1024 count=$$((16 * 1024)) +``` + +Enfin, un dernier point important : pour que vos tests soient +pertinents, sur les machines du parc pédagogique de l'IUT, assurez vous +que les fichiers sources et destinations se situent sur un dossier local +de la machine. Dans le cas contraire, les performances seront dominées +par les latences du réseau et ne permettront pas d'observer la +différence entre les deux techniques d'entrées-sorties. + +``` c +#include <fcntl.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +/** + * copy the file using systems io + */ +void copy_with_read() +{ + int src = open("in.bin", O_RDONLY, S_IRUSR|S_IWUSR); + int dst = open("out.bin", O_WRONLY|O_TRUNC|O_CREAT, S_IRUSR|S_IWUSR); + + char buff[4096]; + ssize_t nread; + + while((nread = read(src, buff, 4096)) > 0) + { + write(dst, buff, nread); + } + + close(src); + close(dst); +} + +/** + * copy the file without transfer to user space + */ +void copy_with_mmap() +{ + // TODO + + return; +} + +int main(void) +{ + clock_t begin, end; + const int LOOPS = 100; + + begin = clock(); + for(int i=0; i<LOOPS; i++) + { + copy_with_read(); + } + end = clock(); + fprintf(stdout, "with std io: %e\n", (double)(end-begin) / CLOCKS_PER_SEC); + + begin = clock(); + for(int i=0; i<LOOPS; i++) + { + copy_with_mmap(); + } + end = clock(); + fprintf(stdout, "with mapped io: %e\n", (double)(end-begin) / CLOCKS_PER_SEC); + + fprintf(stdout, "done.\n"); + exit(0); +} +``` + +## Calcul parallèle et mémoire partagée + +L'ensemble de Mandelbrot est le sous-ensembe des points *c* du plan +complexe pour lesquels la suite *z* converge, où *z(i)* est défini par : + + - *z(0) = 0 + i0* + - *z(n+1) = z(n)² + c* + +C'est un ensemble fractal, c'est-à -dire que sa structure est invariante +par changement d'échelle. Sa définition a été formulée par Adrien +Douadi, qui l'a nommé ainsi en hommage à Benoît Mandelbrot, l'un des +pionniers de la théorie des fractals. + +Un exercice classique en algorithmique consiste à calculer et visualiser +une approximation discrète de cet ensemble. Étant donnée une matrice +dont les cellules sont associées à des pixels, il s'agit de calculer si +le pixel appartient ou non à l'ensemble. Pour cela, on associe à chaque +pixel les coordonnées d'un des points du plan qu'il représente, et on +applique un algorithme d'approximation pour déterminer si ce point +appartient ou non à l'ensemble. Cet algorithme est paramétré par une +précision *p*. Il consiste, étant donné un point *c*, à calculer la +suite des valeurs *z(n)* jusqu'à *n=p*, ou jusqu'à ce que *|z(n)| \> 2* +(car on sait qu'alors la suite *z* diverge). Le pixel est marqué comme +appartenant à l'ensemble si *z(p)* est inférieur ou égal à 2. Pour les +points n'appartenant pas à l'ensemble, il est possible de choisir une +couleur en fonction de l'itération *k* à laquelle *z(k)* devient +supérieur à 2. + +Cet algorithme est implanté dans le fichier source ci-dessous. Votre +travail consiste à paralléliser ce calcul, c'est-à -dire à répartir le +travail entre plusieurs processus. Sur une machine disposant de +plusieurs processeurs, cela permet d'effectuer le calcul plus +rapidement. + +Pour cela, suivre les étapes suivantes : + +1. Étudier le code existant. +2. Faire en sorte que la variable `set` soit accessible depuis + plusieurs processus. +3. Créer plusieurs processus et leur répartir le travail. Pour + l'exercice, répartir le travail entre quatre processus enfants, le + processus parent ne s'occupant que de l'affichage. +4. Faire en sorte que l'affichage de l'image ne se fasse qu'une fois + qu'on est certain que le calcul est terminé. + +Le programme affiche les ressources consommées. Selon la valeur de *p* +et le nombre de cœur de calcul de la machine, la version parallèle est +plus rapide ou plus lente que la version séquentielle. Expliquer +pourquoi. + +``` c +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/mman.h> +#include <sys/resource.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <time.h> +#include <unistd.h> + +#include <errno.h> +#include <string.h> + +const int WIDTH = 5400; /* x resolution in pixels */ +const int HEIGHT = 3600; /* y resolution in pixels, y = (2/3)x */ +const unsigned short PRECISION = 1000; + +/* distance between two pixels */ +double deltax; +double deltay; + +/* viewport */ +const double xmin = -2.0; +const double xmax = 1.0; +const double ymin = -1.0 ; +const double ymax = 1.0; + +unsigned short * set; +time_t stamp; + +void compute_lines(int start, int end) +{ + /* loop counters */ + int i, j, iteration; + + for (i=start; i<end; i++) + { + double yc = ymin + i*deltay; + + for (j=0; j<WIDTH; j++) + { + double xc = xmin + j*deltax; + + double x = 0.0; + double y = 0.0; + iteration = 0; + while((iteration < PRECISION) && ((x*x + y*y) <= 4.0)) + { + iteration++; + double newx = x*x - y*y + xc; + double newy = 2.0*x*y + yc; + x = newx; + y = newy; + } + set[i*WIDTH+j] = iteration; + } + } +} + +void save_to_file (void) +{ + FILE *image; + unsigned short buf[3]; + const unsigned short COLOR_COEFFICIENT = USHRT_MAX / PRECISION; + unsigned short color; + unsigned short iteration; + + image = fopen("output.ppm", "wb"); + if (image == NULL) + { + fprintf(stderr, "Cannot open output file: %s\n", strerror(errno)); + exit(3); + } + + fprintf(image, "P6\n"); // binary + fprintf(image, "%d %d\n", WIDTH, HEIGHT); // image dimension + fprintf(image, "%u\n",USHRT_MAX); // color depth: 32 bits + + for(int i=0; i<HEIGHT; i++) + { + for(int j=0; j<WIDTH; j++) + { + iteration = set[i*WIDTH+j]; + + if (iteration == PRECISION) color = USHRT_MAX; + else color = iteration * COLOR_COEFFICIENT; + + buf[0] = color; + buf[1] = color; + buf[2] = color; + fwrite(buf, sizeof(short), 3, image) ; + } + } + fclose(image); +} + +int main () +{ + + struct rusage stats; + + deltax = (xmax - xmin)/(double)WIDTH; + deltay = (ymax - ymin)/(double)HEIGHT; + + set = (unsigned short*)malloc(WIDTH*HEIGHT*sizeof(short)); + + compute_lines(0, HEIGHT); + + getrusage(RUSAGE_SELF, &stats); + + printf("user time: %ld:%.6ld\n", stats.ru_utime.tv_sec, stats.ru_utime.tv_usec); + printf("system time: %ld:%.6ld\n", stats.ru_stime.tv_sec, stats.ru_stime.tv_usec); + + save_to_file(); + + return (0); +} +```