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
etp1
: La taille du fichierp0
est égale à la taille du fichierp1
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
etp2
: La taille du fichierp1
est inférieure à la taille du fichierp2
car dans le fichierp2
nous avons déclaré une variable globale auquelle nous avons statiquement alloué un espace mémoire. - Entre
p2
etp3
: La taille du fichierp2
est inférieure à la taille du fichierp3
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 implicitementNULL
.
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
etprb
, 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 danspra
etprb
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 de4o
qui s’ajoute à l’espace mémoire nécessaire pour le programme ce qui implique que la taille de l’éxecutable a augmenté de4Ko
car la granularité est de4Ko
. -
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 les4Mo
est la zone des données statiques en lecture écriture du programme. - Pour
prb
: la zone de la mémoire qui contient les4Mo
est la zone du code machine. - Pour
prc
: la zone de la mémoire qui contient les4Mo
est la zone du tas. - Pour
prd
: la zone de la mémoire qui contient les4Mo
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