Page content

Tailles des binaires

Code source des programmes de test

Programme p0.c
int main(int argv, char *argc[]){
	return 42;
}
Programme p1.c
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

int main(int argv, char *argc[]){
	return 42;
}
Programme p2.c
char tableau[8000];

int main(int argv, char *argc[]){
	return 42;
}
Programme p3.c
char tableau[8000]="hello";

int main(int argv, char *argc[]){
	return 42;
}
Comparaison entre les différents programmes
  • Entre p0 et p1 : La taille du fichier p0 est égale à la taille du fichier p1 car lorsqu’on inclue des “Headers”, ceux-ci ne contiennent que des éléments tels que des prototypes de fonctions, des définitions de structures, … . Lorsqu’on ne fait appel à aucune fonction dont le prototype est dans le header, dans ce cas le compilateur ne va rien ajouter lors de la création de l’exécutable.
  • Entre p1 et p2 : La taille du fichier p1 est inférieure à la taille du fichier p2 car dans le fichier p2 nous avons déclaré une variable globale auquelle nous avons statiquement alloué un espace mémoire.
  • Entre p2 et p3 : La taille du fichier p2 est inférieure à la taille du fichier p3 car la valeur par laquelle nous avons initialisé le tableau sera stockée dans le fichier exécutable même si nous avons initialisé par "hello" un tableau de 8000 cases, le reste des cases est implicitement NULL.

Taille mémoire des processus

Programme qui permet d’afficher les adresses de fin de segments de texte, des données initialisées, des données non initialisées et du tas:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

extern char etext, edata, end;

int main(int argv, char *argc[]){
	printf("   etext %p\n", &etext);
	printf("   edata %p\n", &edata);
	printf("   end   %p\n", &end);
	printf("   sbrk  %p\n", sbrk(0));
	pause();
	return 42;
}

Pour plus de détails man 3 etext (ou man 3 edata) et man 2 sbrk.

Programme pra.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

char buf[1024*1024*4] = "Hello";

extern char etext, edata, end;

int main(int argv, char *argc[]){
	printf("   etext %p\n", &etext);
	printf("   edata %p\n", &edata);
	printf("   end   %p\n", &end);
	printf("   sbrk  %p\n", sbrk(0));
	pause();
	return 42;
}
Programme prb.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

const char buf[1024*1024*4] = "Hello";

extern char etext, edata, end;

int main(int argv, char *argc[]){
	printf("   etext %p\n", &etext);
	printf("   edata %p\n", &edata);
	printf("   end   %p\n", &end);
	printf("   sbrk  %p\n", sbrk(0));
	pause();
	return 42;
}
Programme prc.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

extern char etext, edata, end;

int main(int argv, char *argc[]){
	char *buf = malloc(1024*1024*4);
	printf("   etext %p\n", &etext);
	printf("   edata %p\n", &edata);
	printf("   end   %p\n", &end);
	printf("   sbrk  %p\n", sbrk(0));
	pause();
	return 42;
}
Programme prd.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

extern char etext, edata, end;

int main(int argv, char *argc[]){
	char buf[1024*1024*4] = "Hello";
	printf("   etext %p\n", &etext);
	printf("   edata %p\n", &edata);
	printf("   end   %p\n", &end);
	printf("   sbrk  %p\n", sbrk(0));
	pause();
	return 42;
}
Comparaison de la taille en mémoire des différents programmes en utilisant ps -o vsz [pid]
  • On remarque que pour pra et prb, le fait de déclarer notre tableau statique global comme étant constant ou variable n’impacte pas la taille mais uniquement les droits sur l’espace mémoire.

  • On remarque que pour prc, on a alloué un tableau de la même taille que celui dans pra et prb mais vue qu’il est alloué dynamiquement on a besoin d’un pointeur dans lequel on va stocker l’adresse du tableau. La taille du pointeur est de 4o qui s’ajoute à l’espace mémoire nécessaire pour le programme ce qui implique que la taille de l’éxecutable a augmenté de 4Ko car la granularité est de 4Ko.

  • On remarque que pour prd, sa taille mémoire est plus petite que les autres, ceci est principalement dû au fait que le programme réutilise la mémoire libérée de la pile.

Identification de la zone de la mémoire qui est 4Mo plus grosse, en utilisant pmap [pid]
  • Pour pra : la zone de la mémoire qui contient les 4Mo est la zone des données statiques en lecture écriture du programme.
  • Pour prb : la zone de la mémoire qui contient les 4Mo est la zone du code machine.
  • Pour prc : la zone de la mémoire qui contient les 4Mo est la zone du tas.
  • Pour prd : la zone de la mémoire qui contient les 4Mo est la zone de la pile.
Comparaison des adresses de pmap avec celles affichées par le programme
  • Pour le programme pra en triant les adresses on trouve:
557bc299a000      4K r---- pra
557bc299b000      4K r-x-- pra
557bc299b295   adresse retournée par `etext`
557bc299c000      4K r---- pra
557bc299d000      4K r---- pra
557bc299e000   4100K rw--- pra
557bc2d9e020   adresse retournée par `edata`
557bc2d9e028   adresse retournée par `end`
557bc4443000    132K rw---   [ anon ]
557bc4464000   adresse retournée par `sbrk`
7f5a193a6000    148K r---- libc-2.31.so
...

En triant les adresses on arrive bien à distinguer les différents segments de la mémoire qui sont,

  • le code du programme,
  • les données initialisées,
  • les données non initialisées(BSS) et
  • le tas.
Extra

TODO

Utilisation du parallélisme en C

Le programme “p_prime”

#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<math.h>
#include<pthread.h>


//variables globales
bool *work_list = NULL;
long int nb_thread ;
long int maximum ;

// Chaque thread travaille sur une fraction du tableau
// Chacun commence à un indice différent puis "saute" par-dessus les autres
void *do_work(void *ptr)
{
	long int depart = (long int)ptr;
	bool is_prime= false;

	for(long int i = depart; i <= maximum; i+= nb_thread)
	{
		is_prime = true;
		// C'est inefficace, car on parcourt tous les entiers
		// au lieu de tester seulement les nombres premiers
		// mais c'est plus simple à coder (pas de synchronisation nécessaire)
		for(long int j = 2; j <= (long int )sqrt((double)i); j++)
		{
			if(i%j == 0){
				is_prime = false;
				break;
			}
		}

		if(is_prime){
			work_list[i] = true;
		}
	}
	return NULL; //pour faire taire les warnings du compilo
}

int main(int argc, char **argv)
{
	int nb=0, i, depart_argument;

	if (argc < 3) {
		fprintf(stderr, "Vous devez fournir la borne supérieure et le nombre de threads\n");
		return 1;
	}
	char* endptr = NULL;
	maximum = strtol(argv[1], &endptr, 0); // Entier maximum à tester
	if (*endptr != '\0' || maximum <= 0) {
		fprintf(stderr, "La borne supérieure doit être un nombre entier supérieur à 0\n");
		return 1;
	}
	endptr = NULL;
	nb_thread = strtol(argv[2], &endptr, 0); // Nombre de threads à utiliser
	if (*endptr != '\0' || nb_thread <= 0) {
		fprintf(stderr, "Le nombre de threads doit être un nombre entier supérieur à 0\n");
		return 1;
	}

	work_list = malloc(sizeof(bool)*(maximum+1) ); // Allocation de la liste de travail
	if (work_list == NULL) {
		fprintf(stderr, "Erreur lors de l'allocation");
	}

	// Création de l'ensemble des threads, qui exécuteront la méthode `do_work`.
	pthread_t *tableau_id_thread = NULL ;
	tableau_id_thread = malloc(sizeof(pthread_t) * (nb_thread)) ;
	if (tableau_id_thread == NULL) {
		fprintf(stderr, "Erreur lors de l'allocation");
	}

	for (i = 0; i != nb_thread; i++){
		depart_argument = i+2 ;
		pthread_create( tableau_id_thread + i, NULL , do_work, (void *) depart_argument) ;

	}

	// Attendre que l'ensemble des threads soit terminé.
	for (i = 0; i != nb_thread; i++){
		pthread_join( tableau_id_thread[i], NULL ) ;
	}

	// Comptage du nombre de nombres premiers trouvés
	for(i = 2; i <= maximum; i++) {
		if(work_list[i]){
			nb++;
		}
	}

	printf("Nombre de nombres premiers trouvés : %d\n", nb) ;

	return 0;
}
  • Effectivement on obtient les mêmes résultats.
  • On remarque que notre programme a utilisé l’appel système clone pour créer les threads.
  • On remarque que la version multithread est plus lente que la version monothread car ceci est principalement du au fait que le processeur doit gérer en plus l’aspect du parallélisme des threads.
  • Conclusion : la programmation multithread n’est pas simple et n’implique pas de meilleurs performance systèmatiquement.
Extra

TODO