IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C Discussion :

Pourquoi le temps d'exécution d'une multiplication matricielle varie-t-il ?


Sujet :

C

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre habitué
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2014
    Messages
    11
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 31
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2014
    Messages : 11
    Par défaut Pourquoi le temps d'exécution d'une multiplication matricielle varie-t-il ?
    Bonjour,
    J'aimerais que l'on m'éclaire sur une mesure du temps d'exécution d'un calcul.

    J'utilise un programme qui fait une multiplication matricielle avec de grandes matrices (2500*2500) et je mesure le temps d'exécution de la multiplication matricielle.
    Pour exécuter mon programme je n'utilise qu'un seul core à une fréquence fixe de 2GHz.

    En exécutant plusieurs fois d'affilé mon programme j'obtiens les temps d'exécution suivants :
    262.52 sec
    186.84
    108.56
    109.81
    109.02 … Le temps se stabilise autour de 109s.
    J'aimerais comprendre pourquoi le temps d'exécution diminue autant et se stabilise au bout de quelques exécutions.

    Remarque 1 : Voici comment est organisé mon programme :
    Allocation mémoire (malloc), initialisation des matrices,
    Mesure du temps start, calcul matriciel, mesure du temps end et calcul du temps d'exécution t=end-start,
    Libération de la mémoire (free)

    Remarque 2 :
    Pour éviter d'avoir l'influence les données gardées en cache et mesurer seulement le temps de calcul, j'ai fait les considérations suivantes :
    D'une part, j'utilise de très grandes matrices, il n'est plus possible de les garder dans le cache. Les données sont alors chargées depuis la RAM et le temps de chargement depuis la RAM devrait à peu près être constant.
    De plus je ne mesure le temps que sur la partie du code qui fait le calcul matriciel et je considère que le temps d'allocation mémoire et d'initialisation est négligeable devant le temps de calcul.

    Edit :
    Voici le code de mon programme.
    Pour changer le signe de la matrice, il faut changer int a_r=2500,a_c=2500,b_r=2500,b_c=2500;
    Je le met en pièce jointe avec les scripts shell. (il s'agit d'un projet Eclipse)
    ./bench1 lance 2 fois ./bench2 avec une tempo sleep entre les 2.
    ./bench2 lance 4 fois le programme d'affilé. Le temps d'exécution est long (au moins 5 min) et les données sont sauvegardées dans un fichier texte.
    Les options de compilation sont g++ -O3 -Wall -c

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
     
    #include <stdlib.h>
    #include <stdio.h>
    #include <time.h>
    #include <float.h>
    #include <sys/time.h>
    #include <sys/mman.h>
    #include <sys/resource.h>
     
    /****************************************************
     * Random generate a random int between _Min and _Max
     */
    int Random(int _iMin,int _iMax)
    {
    	return (_iMin + (rand()%(_iMax-_iMin+1)));
    }
     
    /****************************************************
     * Return the struct of getrusage.
     */
    struct rusage give_ru()
    {
        struct rusage ru;
        getrusage(RUSAGE_SELF, &ru);
        //getrusage(RUSAGE_CHILDREN, &ru);
     
        return ru;
    }
     
    /****************************************************
     * Do a matrix multiplication a*b=c
     * This program isn't optimized.
     */
    int main()
    {
    	/*
    	 * Initialize variables and array sizes.
    	 */
    	int **a,**b,**c;
    	printf("\nenter Taille de la matrice : 2500");
    	int a_r=2500,a_c=2500,b_r=2500,b_c=2500;
    	clock_t start,end;
    	struct rusage start1,end1;
    	/* variables to store time difference between
    	start of matrix multiplication and end of multiplication. */
    	double dif; /*variable to calculate the time difference between the parallelization */
    	int i,j,k;
     
    	again:
    	printf("\nenter rows and columns for matrix one:");
    	//scanf("%d%d",&a_r,&a_c);
    	printf("\nenter rows and columns for matrix two:");
    	//scanf("%d%d",&b_r,&b_c);
     
    	if(a_c!=b_r )
    	{
    		printf("\ncan not multiply");
    		goto again;
    	}
     
     
    	/*
    	 * Memory allocation.
    	 */
    	start =clock();
    	/* allocate memory for matrix one */
    	a=(int **) malloc(10*a_r);
    	for( i=0;i<a_c; i++)
    	{
    		a[i]=(int *) malloc(10*a_c);
    	}
    	/* allocate memory for matrix two */
    	b=(int **) malloc(10*b_r);
    	for( i=0;i<b_c; i++)
    	{
    		b[i]=(int *) malloc(10*b_c);
    	}
    	/* allocate memory for sum matrix */
    	c=(int **) malloc(10*a_r);
    	for( i=0;i< b_c; i++)
    	{
    		c[i]=(int *) malloc(10*b_c);
    	}
     
    	/* Memory is lock*/
    	printf("Starting mlockall.\n");
    	if(mlockall(MCL_CURRENT | MCL_FUTURE)<0) {
    		perror("mlockall");
    		return 1;
    	}
     
     
    	/*
    	 * Matrix initialization (with random integer).
    	 */
    	printf("Initializing matrices...\n");
     
    	//initializing first matrix
    	for(i=0;i<a_r; i++)
    	{
    		for(j=0;j<a_c; j++)
    		{
    			a[i][j] = Random(0,100);//i+j;
    		}
    	}
    	// initializing second matrix
    	for(i=0;i<b_r; i++)
    	{
    		for(j=0;j<b_c; j++)
    		{
    			b[i][j] = Random(0,100);//i*j;
    		}
    	}
    	/*initialize product matrix */
    	for(i=0;i<a_r; i++)
    	{
    		for(j=0;j< b_c; j++)
    		{
    			c[i][j]=Random(0,100);//0;
    		}
    	}
    	end= clock(); //end the timer
    	dif = ((double) (end - start)) / CLOCKS_PER_SEC; //store the difference
    	printf("Malloc and initialization took %f sec. time.\n", dif);
     
     
    	/*
    	 * Matrix Multiplication.
    	 */
    	start =clock(); //start the timer
    	start1 = give_ru(); //start the timer
    	/* multiply matrix one and matrix two */
    	for(i=0;i<a_r; i++)
    		for(j=0;j<a_c; j++)
    			for(k=0;k<b_c; k++)
    			c[i][j]=c[i][j]+a[i][k]*b[k][j];
     
    	end1 = give_ru(); //end the timer
    	end = clock(); //end the timer
     
    	dif = ((double) (end - start)) / CLOCKS_PER_SEC; //store the difference
     
    	struct timeval timeUserStart = start1.ru_utime;
    	double utimeStart = (double)timeUserStart.tv_sec + (double)timeUserStart.tv_usec / 1000000.0;
    	struct timeval timeSystemStart = start1.ru_stime;
    	double stimeStart = (double)timeSystemStart.tv_sec + (double)timeSystemStart.tv_usec / 1000000.0;
     
    	struct timeval timeUserEnd = end1.ru_utime;
    	double utimeEnd  = (double)timeUserEnd.tv_sec + (double)timeUserEnd.tv_usec / 1000000.0;
    	struct timeval timeSystemEnd  = end1.ru_stime;
    	double stimeEnd  = (double)timeSystemEnd.tv_sec + (double)timeSystemEnd.tv_usec / 1000000.0;
     
    	double difuTime = utimeEnd - utimeStart;
    	double difsTime = stimeEnd - stimeStart;
     
    	long pageReclaims = end1.ru_minflt;//start1.ru_minflt-
    	long pageFaults = end1.ru_majflt;//start1.ru_majflt-
     
    	printf("Parallelization took %f sec. time.\n", dif);
    	printf("Time User : %f .\n", difuTime );
    	printf("Time System : %f .\n", difsTime );
    	printf("Page reclaims : %ld .\n", end1.ru_minflt);
    	printf("Page faults : %ld .\n", end1.ru_majflt);
     
    	/*
    	 * free memory.
    	 */
    	printf("Finished, cleaning up \n");
    	munlockall();
     
    	/*free memory*/
    	for(i=0;i<a_r; i++)
    	{
    		free(a[i]);
    	}
    	free(a);
    	for(i=0;i<a_c; i++)
    	{
    		free(b[i]);
    	}
    	free(b);
    	for(i=0;i<b_c; i++)
    	{
    		free(c[i]);
    	}
    	free(c);
     
    	return 0;
    }
    Fichiers attachés Fichiers attachés

  2. #2
    Modérateur
    Avatar de dinobogan
    Homme Profil pro
    ingénieur
    Inscrit en
    Juin 2007
    Messages
    4 073
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 44
    Localisation : France

    Informations professionnelles :
    Activité : ingénieur
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juin 2007
    Messages : 4 073
    Par défaut
    C'est peut-être un "problème" de swap. Ton programme utilise peut-être plus de RAM que ce qui est libre et il faut du temps pour que l'OS déplace des programmes dans la swap.
    Sur un UNIX AIX IBM, un programme en swap ne revient pas en RAM. C'est surement le cas sur ton OS.
    Si tu en a la possibilité, redémarre la machine. Fais la liste des programmes en swap. Puis après chaque lancement de ton programme, vérifie les programmes qui ont migré en swap.
    Toujours si tu en la possibilité, après un nouveau redémarrage de la machine, arrête les programmes qui avaient migré en swap et relance un test.
    N'oubliez pas de consulter les FAQ Java et les cours et tutoriels Java
    Que la force de la puissance soit avec le courage de ta sagesse.

  3. #3
    Membre habitué
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2014
    Messages
    11
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 31
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2014
    Messages : 11
    Par défaut
    Salut Dinobogan (j'adore ton pseudo),
    Ce n'est pas un problème de swap.

    Mon cache L3 fait 12Mo et ma RAM 32Go.
    La taille des matrices lors de la multiplication est 2500*2500 (taille de la matrice) * 4 (taille d'un int en o sur mon ordi) * 3 (il y a trois matrices a, b et c) = 75Mo.
    Donc a priori j'accède à la RAM mais pas au swap.

    Au cas où, j'ai désactivé le swap mais il n'y a pas de changements.

  4. #4
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Chercheur d'emploi
    Inscrit en
    Septembre 2007
    Messages
    7 496
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Chercheur d'emploi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 496
    Par défaut
    Bonjour,
    Peut-on connaître également le type de machine que tu utilises et son système d'exploitation ? J'imagine que c'est un PC 64 bits.

    À ce stade, il y a plusieurs choses à prendre en considération :

    • La méthode que tu utilises pour mesurer le temps d'exécution. En particulier, il faudra distinguer le temps réellement consacré à ton processus et celui passé en mode noyau (pour quelque raison que ce soit). Il faudra aussi distinguer celui passé dans ton processus et celui passé dans les autres. Si tu récupères simplement la valeur de l'horloge, toutes ces mesures sont confondues ;
    • L'ordre dans lequel tu accèdes à tes données. Il est tout-à-fait possible des sauts un peu trop aléatoires pourrissent ta ligne de cache et que lorsqu'elle est un peu mieux agencée, des raccourcis soient possibles ;
    • La manière dont fonctionnent les processus à côté. La ligne de cache est la même pour tout le monde, donc s'il y en a un qui, sans être dans le swap, sort de son sommeil un peu trop souvent, cela puisse influer également ;
    • La façon dont tu fais tes calculs : est-ce qu'il y a des raccourcis possibles en cas de matrice creuse ? Et d'une manière générale, les données elles-mêmes peuvent-elles influer sur la durée et la complexité de ton calcul ?

  5. #5
    Membre habitué
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2014
    Messages
    11
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 31
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2014
    Messages : 11
    Par défaut
    Bonjour Obsidian,

    Tout d'abord j'aimerais expliquer mon problème, dans le cadre de mes études, j'essaie de trouver pourquoi un programme (que je n'ai pas développé) à un temps d'exécution variable. Pour ce faire je me suis ramené au cas d'une multiplication matricielle. Effectivement comme tu l'as fait remarquer, mon calcul matriciel n'est pas optimisé. J'ai fait ce choix dans un premier temps pour voir si déjà dans ce cas "simple", il y avait déjà des variations de temps et comment on pouvait les expliquer.

    Citation Envoyé par Obsidian Voir le message
    Peut-on connaître également le type de machine que tu utilises et son système d'exploitation ? J'imagine que c'est un PC 64 bits.
    Je travaille sur un Xeon E5-2620, avec 2 cpus possédants 6 cores chacun et possibilité d'utiliser l'hyperthreading; et un OS Red Hat.


    Citation Envoyé par Obsidian Voir le message
    La méthode que tu utilises pour mesurer le temps d'exécution. En particulier, il faudra distinguer le temps réellement consacré à ton processus et celui passé en mode noyau (pour quelque raison que ce soit). Il faudra aussi distinguer celui passé dans ton processus et celui passé dans les autres. Si tu récupères simplement la valeur de l'horloge, toutes ces mesures sont confondues ;
    J'ai testé plusieurs méthodes pour mesurer le temps : avec gettimeofday() et clock()/CLOCKS_PER_SEC
    Après j'ai trouvé ce document qui propose d'utiliser la fonction getrusage pour distinguer le temps user du temps system. De plus en utilisant l'argument RUSAGE_SELF je mesure le temps du process. Danc je pense que c'est bon de ce côté là.


    Citation Envoyé par Obsidian Voir le message
    L'ordre dans lequel tu accèdes à tes données. Il est tout-à-fait possible des sauts un peu trop aléatoires pourrissent ta ligne de cache et que lorsqu'elle est un peu mieux agencée, des raccourcis soient possibles ;
    Très bonne remarque Mon programme n'est pas du tout optimisé, c'est un choix volontaire comme je m'intéresse à pourquoi le temps d'exécution varie dans différent cas (comme je l'expliquais au début du message).
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    for(i=0;i<a_r; i++)
    		for(j=0;j<a_c; j++)
    			for(k=0;k<b_c; k++)
    			c[i][j]=c[i][j]+a[i][k]*b[k][j];
    Cependant cela pourrait être la cause de "l'amélioration" du temps d'exécution que j'observe. Est-ce qu'il est possible que la mémoire puisse améliorer son comportement d'un lancement à l'autre ? Cela semble une très bonne piste mais je fais make clean, make, malloc et free à chaque lancement. Est ce qu'il est capable de retrouver les données en mémoire même après free et make clean ?

    J'ai commencé à optimiser mon code et il semblerait que le temps d'exécution devienne constant mais ce que je veux comprendre c'est pourquoi le temps n'est pas constant dans le cas non optimisé.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    	for(i=0;i<a_r; ++i)
    		for(j=0;j<a_c; ++j)
    			tmp[i][j]=b[j][i];
     
    	for(i=0;i<a_r; ++i)
    		for(j=0;j<a_c; ++j)
    			for(k=0;k<b_c; ++k)
    			c[i][j]+=a[i][k]*tmp[j][k];
    Citation Envoyé par Obsidian Voir le message
    La manière dont fonctionnent les processus à côté. La ligne de cache est la même pour tout le monde, donc s'il y en a un qui, sans être dans le swap, sort de son sommeil un peu trop souvent, cela puisse influer également ;
    A priori non. Il n'y a qu'un tableur LibreOffice d'ouvert. De temps en temps il se réveille pour sauvegarder automatiquement le document. Je n'observe rien d'autre avec la commande top.

    Citation Envoyé par Obsidian Voir le message
    La façon dont tu fais tes calculs : est-ce qu'il y a des raccourcis possibles en cas de matrice creuse ? Et d'une manière générale, les données elles-mêmes peuvent-elles influer sur la durée et la complexité de ton calcul ?
    Mes matrices sont toujours initialisées de la même façon, la matrice a est rempli par i+j et b par i*j avec i et j les numéros de la ligne et de la colonne respectivement.

  6. #6
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Chercheur d'emploi
    Inscrit en
    Septembre 2007
    Messages
    7 496
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 49
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Chercheur d'emploi
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 496
    Par défaut
    LibreOffice est un bloatware qui va consommer beaucoup de mémoire. Même si tu disposes de 32 Go, ce n'est jamais une bonne idée de lancer un benchmark avec ces choses-là ouvertes à côté. Dans l'absolu, si tu as l'exclusivité de l'usage de ta machine, je la relancerais en « init 1 » et referais mes mesures depuis ce mode. Idéalement, il faudrait même passer cela en tant qu'option du noyau au démarrage pour atterrir directement dans ce mode avec un système vierge.

    Pour le reste, getrusage() est effectivement la bonne approche, mais as-tu comparé le temps système et le temps utilisateur ? Par ailleurs, les mesures que tu lis sont-elles cohérentes avec ce que tu observes montre en main depuis ton bureau ?

    Il y a certainement une corrélation entre la ligne de cache et le temps d'exécution (tu as au moins 12 mégas de données personnelles pré-chargées à la seconde exécution) mais ça ne devrait pas être dans ces proportions non plus. Par contre, il serait très intéressant d'exhiber la corrélation éventuelle entre les temps d'exécution et les valeurs relevées dans r_minflt et r_majflt.

    Enfin, dans le cas particulier où tu gérerais des matrices carrées, ces questions de mémoire cache feraient que tu gagnerais largement à effectuer A=A×B (quitte à faire une copie de A, linéairement au départ) plutôt que C=A×B. D'autre part, puisque les règles de la multiplication matricielle nous obligent à confronter une ligne de A avec une colonne de B, on aurait également beaucoup à gagner à travailler en fait sur la transposée de B de manière à ce que les données d'une colonne restent consécutives en mémoire.

  7. #7
    Modérateur
    Avatar de gangsoleil
    Homme Profil pro
    Manager / Cyber Sécurité
    Inscrit en
    Mai 2004
    Messages
    10 150
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Manager / Cyber Sécurité

    Informations forums :
    Inscription : Mai 2004
    Messages : 10 150
    Par défaut
    Citation Envoyé par Paquito_Fr Voir le message
    Est-ce qu'il est possible que la mémoire puisse améliorer son comportement d'un lancement à l'autre ? Cela semble une très bonne piste mais je fais make clean, make, malloc et free à chaque lancement. Est ce qu'il est capable de retrouver les données en mémoire même après free et make clean ?
    Ca ne sert a rien : si tu ne modifies pas les sources, le resultat produit par le compilateur sera exactement le meme (et heureusement). Si tu veux en etre convaincu, regarde le resultat d'un checksum sur tes executables.

    Maintenant, j'aimerai etre certain que tu ne mesures pas le temps d'allocation de la memoire dans tes temps. En effet, lorsque tu quittes un programme, meme si celui-ci est vraiment mort et enterre, la memoire qu'il occupait n'est, en realite, pas liberee mais marquee comme disponible. Cela permet, si le programme est relance dans un temps relativement proche, de ne pas devoir reallouer la memoire, ni recharger les donnees.
    Qui plus est, tu dis que tes matrices sont toujours identiques : si c'est vraiment le cas, alors oui, tes donnees sont toujours en RAM, et ne seront pas reellement rechargees entre deux execution.

    Solution 1 : utiliser un programme qui libere vraiment la memoire -- du genre memcleaner si ca existe encore et si ca existe pour linux.

    Solution 2 : utiliser des valeurs "random" pour initialiser ta matrice.
    "La route est longue, mais le chemin est libre" -- https://framasoft.org/
    Les règles du forum

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Réponses: 3
    Dernier message: 11/03/2006, 15h35
  2. Différences de temps d'exécution d'une même requête
    Par hellbilly dans le forum Access
    Réponses: 3
    Dernier message: 16/12/2005, 20h56
  3. Temps d'exécution d'une vue
    Par mic79 dans le forum Langage SQL
    Réponses: 3
    Dernier message: 05/09/2005, 16h50
  4. Réponses: 9
    Dernier message: 20/06/2005, 12h17
  5. Affichage du temps d'exécution d'une requête
    Par milka dans le forum PostgreSQL
    Réponses: 1
    Dernier message: 22/03/2004, 17h48

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo