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

  1. #1
    Membre à l'essai
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2014
    Messages
    11
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 30
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2014
    Messages : 11
    Points : 14
    Points
    14
    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 : 42
    Localisation : France

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

    Informations forums :
    Inscription : Juin 2007
    Messages : 4 073
    Points : 7 163
    Points
    7 163
    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 à l'essai
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2014
    Messages
    11
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 30
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2014
    Messages : 11
    Points : 14
    Points
    14
    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
    Développeur en systèmes embarqués
    Inscrit en
    Septembre 2007
    Messages
    7 371
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 371
    Points : 23 626
    Points
    23 626
    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 à l'essai
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2014
    Messages
    11
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 30
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2014
    Messages : 11
    Points : 14
    Points
    14
    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
    Développeur en systèmes embarqués
    Inscrit en
    Septembre 2007
    Messages
    7 371
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 371
    Points : 23 626
    Points
    23 626
    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
    Points : 28 119
    Points
    28 119
    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

  8. #8
    Membre à l'essai
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2014
    Messages
    11
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 30
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2014
    Messages : 11
    Points : 14
    Points
    14
    Par défaut
    Citation Envoyé par Obsidian Voir le message
    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.
    J'obtiens les mêmes résultats :s

    Citation Envoyé par Obsidian Voir le message
    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 ?
    J'obtiens par exemple au premier lancement :
    Time User = 272.64s comme montre en main.
    Time System = 0.0019s
    Pour l'instant je ne comprend pas pourquoi la valeur de time system est aussi faible.

    Citation Envoyé par Obsidian Voir le message
    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.
    J'obtiens minflt = 6000 (environ) et majflt =0 dans tous les cas. Le problème ne semble pas venir d'une erreur de page mais je vais faire des tests supplémentaires avec perf stat.

    Citation Envoyé par Obsidian Voir le message
    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.
    Oui tout à fait. Je n'avais pas pensé à faire A=A*B.
    Cependant mon problème est le suivant : on m'a passé un programme qui fait des calculs matriciels avec de grandes matrices. Comme son temps d'exécution variait de 20% de façon inexpliquée, on m'a demandé de trouver la cause. J'ai donc décider de commencer par une multiplication de matrice non optimisée pour observer si on avait un temps d'exécution fixe. Je me dis que si je comprends ce qui ce passe dans ce cas "simple", ça pourra m'aider pour le programme plus complexe que j'étudie.

    Citation Envoyé par gangsoleil Voir le message
    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.
    Effectivement

    Citation Envoyé par gangsoleil Voir le message
    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.
    Pour m'assurer que je mesurais le temps de calcul et non l'allocation mémoire, j'ai d'abord généré le code assembleur avec gcc -S (ou -Wa). J'ai aussi désassemblé le code avec objdump -d. Enfin, j'ai lancé le programme avec des nombres aléatoires mais sans que le temps d'exécution n'évolue.

    Cependant j'ai remarqué un phénomène étrange.
    Quand je lance une série de mesure à la suite, j'obtiens (entre 200 et 300)s, 180s, 110s, 110s ...
    Si j'attends 6 secondes avant de relancer une série de mesure, j'obtiens (entre 200 et 300)s, 180s, 110s, 110s ...
    Et si j'attends moins de 5 secondes : 110s, 110s ...
    C'est comme si il "oubliait" au bout de quelque secondes

  9. #9
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Septembre 2007
    Messages
    7 371
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 371
    Points : 23 626
    Points
    23 626
    Par défaut
    Citation Envoyé par Paquito_Fr Voir le message
    J'obtiens par exemple au premier lancement :
    Time User = 272.64s comme montre en main.
    Time System = 0.0019s
    Pour l'instant je ne comprend pas pourquoi la valeur de time system est aussi faible.
    C'est normal. Tu fais tous tes calculs toi-même et en userspace. Les appels système que tu passes pendant le processus sont très peu nombreux et quand tu en fais, ils sont honorés très rapidement. Ces résultats sont cohérents.

    Oui tout à fait. Je n'avais pas pensé à faire A=A*B.

    Cependant mon problème est le suivant : on m'a passé un programme qui fait des calculs matriciels avec de grandes matrices. Comme son temps d'exécution variait de 20% de façon inexpliquée, on m'a demandé de trouver la cause. J'ai donc décider de commencer par une multiplication de matrice non optimisée pour observer si on avait un temps d'exécution fixe. Je me dis que si je comprends ce qui ce passe dans ce cas "simple", ça pourra m'aider pour le programme plus complexe que j'étudie.
    On est bien d'accord. :-)

    Cependant j'ai remarqué un phénomène étrange.
    Quand je lance une série de mesure à la suite, j'obtiens (entre 200 et 300)s, 180s, 110s, 110s ...
    Si j'attends 6 secondes avant de relancer une série de mesure, j'obtiens (entre 200 et 300)s, 180s, 110s, 110s ...
    Et si j'attends moins de 5 secondes : 110s, 110s ...
    C'est comme si il "oubliait" au bout de quelque secondes
    Il y a soudain quelque chose qui me vient à l'esprit : j'imagine que ton processeur est fait pour faire du frequency scaling dès lors qu'il commence à être chargé. As-tu pensé à vérifier s'il changeait de fréquence ou non quelques secondes après avoir lancé ton programme ?

  10. #10
    Membre à l'essai
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2014
    Messages
    11
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 30
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2014
    Messages : 11
    Points : 14
    Points
    14
    Par défaut
    Citation Envoyé par Obsidian Voir le message
    Il y a soudain quelque chose qui me vient à l'esprit : j'imagine que ton processeur est fait pour faire du frequency scaling dès lors qu'il commence à être chargé. As-tu pensé à vérifier s'il changeait de fréquence ou non quelques secondes après avoir lancé ton programme ?
    Le frequency scaling est désactivé. Dans /proc/cpuinfo la fréquence est fixe et j'ai aussi une application qui m'affiche la fréquence en direct. Celle-ci reste toujours à 2GHz.

    Merci pour tes conseils. Je vais poster mon code dans mon premier message. J'ai essayer mon code sur plusieurs PC multi-cores et j'obtiens les mêmes résultats.

    Est ce qu'il est possible que le cpu puisse comprendre qu'il fait en boucle du calcul matriciel et qu'il puisse lui-même améliorer ses performances au bout d'un certain temps ?

  11. #11
    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 : 42
    Localisation : France

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

    Informations forums :
    Inscription : Juin 2007
    Messages : 4 073
    Points : 7 163
    Points
    7 163
    Par défaut
    Tous les processeurs (enfin, la grande majorité des processeurs récents) possèdent une unité prédictive. Cependant, cela fonctionne uniquement pour un programme durant le runtime. Si un programme fait plusieurs fois la même chose, ce sera fait de plus en plus rapidement.
    Rien n'est fait au niveau matériel pour deux exécutions successives d'un même programme.
    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.

  12. #12
    Modérateur
    Avatar de Obsidian
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Septembre 2007
    Messages
    7 371
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 47
    Localisation : France, Essonne (Île de France)

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2007
    Messages : 7 371
    Points : 23 626
    Points
    23 626
    Par défaut
    Citation Envoyé par Paquito_Fr Voir le message
    Le frequency scaling est désactivé. Dans /proc/cpuinfo la fréquence est fixe et j'ai aussi une application qui m'affiche la fréquence en direct. Celle-ci reste toujours à 2GHz.
    C'est curieux parce qu'avec des différences de respectivement +70% et +140%, ça me semblait une explication toute trouvée. Essaie quand même de voir si tu retombes sur les chiffres initiaux quand tu relances ton programme après l'avoir arrêté pendant deux ou trois minutes.

    Merci pour tes conseils. Je vais poster mon code dans mon premier message. J'ai essayer mon code sur plusieurs PC multi-cores et j'obtiens les mêmes résultats.
    Est ce qu'il est possible que le cpu puisse comprendre qu'il fait en boucle du calcul matriciel et qu'il puisse lui-même améliorer ses performances au bout d'un certain temps ?
    Pas au niveau du code assembleur. Et même si ton processeur est capable de faire un minimum d'heuristique, le calcul en lui-même est linéaire et très simple. Ça ne devrait pas influer.

    Est-ce que ton programme est multi-threadé et, si oui, combien de fils distincts lances-tu ?

  13. #13
    Membre à l'essai
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2014
    Messages
    11
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 30
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2014
    Messages : 11
    Points : 14
    Points
    14
    Par défaut
    @ Dinobogan et Obsidian
    Effectivement pour l'hypothèse selon laquelle le processeur pourrait optimiser l'assembleur parait un peu tiré par les cheveux.

    Citation Envoyé par Obsidian Voir le message
    C'est curieux parce qu'avec des différences de respectivement +70% et +140%, ça me semblait une explication toute trouvée. Essaie quand même de voir si tu retombes sur les chiffres initiaux quand tu relances ton programme après l'avoir arrêté pendant deux ou trois minutes.

    Est-ce que ton programme est multi-threadé et, si oui, combien de fils distincts lances-tu ?
    Il n'y a qu'un seul thread.
    Je suis en train de faire des tests avec la commande perf stat sur deux ordis différents. Il va me falloir du temps pour analyser les données mais cet outil me confirme que la fréquence est fixe et qu'il n'y a qu'un thread.

    Citation Envoyé par Obsidian Voir le message
    Essaie quand même de voir si tu retombes sur les chiffres initiaux quand tu relances ton programme après l'avoir arrêté pendant deux ou trois minutes.
    Il suffit que j'attends 6 secondes.

    Au final ce qui limite ma vitesse de calcul, c'est surement ma rapidité pour accéder en mémoire. Si j'ai 12Mo de pré-chargé dans le cache L3, c'est peut-être suffisant pour accélérer grandement mon exécution.

  14. #14
    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
    Points : 28 119
    Points
    28 119
    Par défaut
    Citation Envoyé par Paquito_Fr Voir le message
    Il suffit que j'attends 6 secondes.

    Au final ce qui limite ma vitesse de calcul, c'est surement ma rapidité pour accéder en mémoire. Si j'ai 12Mo de pré-chargé dans le cache L3, c'est peut-être suffisant pour accélérer grandement mon exécution.
    Tu peux donc essayer de te renseigner sur la duree de validite d'une page dans le cache, ou en memoire, ou des choses comme ca.

    Tu executes exactement le meme programme, donc tout ce qui est chargé en memoire et encore valide n'a pas besoin d'etre recharge. Ainsi, si tu as des donnees qui restent valides en memoire, ou mieux en cache, tu gagnes le temps de rechargement -- ce qui est loin d'etre negligeable, surtout si ton programme tient en cache.
    Et comme les donnees en memoire ont un temps de validite, c'est une piste.

    Pour t'en abstraire, tu peux realiser le test que je te proposais, c'est a dire effectuer des multiplications de matrices "random" : tu construis tes deux matrices a partir de valeur random a chaque execution. Je pense que dans ce cas, tu auras des temps d'execution plus lineaires.
    "La route est longue, mais le chemin est libre" -- https://framasoft.org/
    Les règles du forum

  15. #15
    Membre à l'essai
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2014
    Messages
    11
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 30
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2014
    Messages : 11
    Points : 14
    Points
    14
    Par défaut
    Citation Envoyé par gangsoleil Voir le message
    Tu executes exactement le meme programme, donc tout ce qui est chargé en memoire et encore valide n'a pas besoin d'etre recharge. Ainsi, si tu as des donnees qui restent valides en memoire, ou mieux en cache, tu gagnes le temps de rechargement -- ce qui est loin d'etre negligeable, surtout si ton programme tient en cache.
    Et comme les donnees en memoire ont un temps de validite, c'est une piste.

    Pour t'en abstraire, tu peux realiser le test que je te proposais, c'est a dire effectuer des multiplications de matrices "random" : tu construis tes deux matrices a partir de valeur random a chaque execution. Je pense que dans ce cas, tu auras des temps d'execution plus lineaires.
    J'avais oublié mais j'utilise déjà des nombres aléatoires donc la théorie selon laquelle il garde une partie des données en mémoire tombe à l'eau.

    Par contre voici les résultats de mes tests avec perf stat :
    Le nombre de page fault est constant quelque soit la durée du calcul.
    Les paramètres suivants sont proportionnels à la durée du calcul : dTLB-load-misses, L1-dcache-store-misses, L2-loads, LLC-loads, LLC-stores.
    Cela veut dire qu'il fait bien plus de load en L2 et en LLC lors des premières exécutions du programme, ce qui augmente son temps d'exécution.

    Du coup, pour l'instant je pense que c'est dû au TLB (Translation Lookaside Buffer). Il retiendrait les pages les plus utilisées.
    Mais du coup cela me pose plusieurs problèmes :
    Comment vérifier que le TLB est à l'origine de ce temps d'exécution ? (est ce qu'il y a un moyen de le vider ?)
    Pourquoi il lui faudrait 2 exécutions du programme pour charger les pages utilisées dans le TLB ? (comme les temps sont 260s, 190s puis 110, 110 ...)
    Est-ce que ce sont les mêmes pages qui sont utilisées à chaque lancement du programme ? (Si ce ne sont pas les mêmes, il ne peut pas les retenir)

  16. #16
    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 : 42
    Localisation : France

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

    Informations forums :
    Inscription : Juin 2007
    Messages : 4 073
    Points : 7 163
    Points
    7 163
    Par défaut
    Si ton code dans ton premier post est complet, alors tu n'initialises pas ton générateur aléatoire.
    Donc toutes tes données sont identiques entre deux lancement du programme.
    Cela pourrait expliquer pourquoi le processeur l'exécute de plus en plus rapidement : les caches internes contiennent déjà les bonnes valeurs.
    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.

  17. #17
    Membre éclairé
    Profil pro
    Ingénieur sécurité
    Inscrit en
    Février 2007
    Messages
    574
    Détails du profil
    Informations personnelles :
    Âge : 39
    Localisation : Etats-Unis

    Informations professionnelles :
    Activité : Ingénieur sécurité
    Secteur : Industrie

    Informations forums :
    Inscription : Février 2007
    Messages : 574
    Points : 751
    Points
    751
    Par défaut
    Citation Envoyé par Paquito_Fr Voir le message
    Comment vérifier que le TLB est à l'origine de ce temps d'exécution ? (est ce qu'il y a un moyen de le vider ?)
    Sur x86, tu peux vider tous les caches via INVD, et juste certaines pages de la TLB via INVLPG.

    Citation Envoyé par Paquito_Fr Voir le message
    Pourquoi il lui faudrait 2 exécutions du programme pour charger les pages utilisées dans le TLB ? (comme les temps sont 260s, 190s puis 110, 110 ...)
    Est-ce que ce sont les mêmes pages qui sont utilisées à chaque lancement du programme ? (Si ce ne sont pas les mêmes, il ne peut pas les retenir)
    Ton programme est charge en page cache a la premiere execution depuis le disque. Les executions suivantes ne chargent pas le programme depuis le disque mais depuis le page cache.
    Pour la RAM, je ne sais pas si les pages physiques sont les memes entre les executions. J'aurais tendances a penser que non, vu que l'addressage virtuel est nouveau. Mias peut etre que Linux ressert les pages en TLB au programme suivant afin d'optimiser l'acces memoire.

    C'est un probleme interessant.

  18. #18
    Membre à l'essai
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2014
    Messages
    11
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 30
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2014
    Messages : 11
    Points : 14
    Points
    14
    Par défaut
    Citation Envoyé par dinobogan Voir le message
    Si ton code dans ton premier post est complet, alors tu n'initialises pas ton générateur aléatoire.
    Donc toutes tes données sont identiques entre deux lancement du programme.
    Cela pourrait expliquer pourquoi le processeur l'exécute de plus en plus rapidement : les caches internes contiennent déjà les bonnes valeurs.
    Effectivement, je ne me souvenais plus qu'il fallait initialiser le générateur ^^. J'ai rajouté srand(time(NULL)); mais je n'ai pas obtenu de différence.
    D'après un de mes professeur, il n'est pas possible que les valeurs puissent être retrouvées dans les caches ou dans la mémoire vive d'un lancement à l'autre :
    1. Il y a peu de chances que la mémoire virtuelle soit allouée à l'identique.
    2. Et peut de chance que la correspondance mémoire virtuelle - mémoire physique soit la même aussi.


    Pour ce qui concerne la TLB, j'ai aussi abandonné l'idée. D'une part, le nombre de page faults ne varie pas beaucoup. (Il est soit de 19482, soit 19496 ). Et d'après Wikipédia, la TLB est effacée régulièrement :
    Citation Envoyé par Wikipedia
    On a context switch, some TLB entries can become invalid, since the virtual-to-physical mapping is different. The simplest strategy to deal with this is to completely flush the TLB. This means that after a switch, the TLB is empty and any memory reference will be a miss

  19. #19
    Membre à l'essai
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2014
    Messages
    11
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 30
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2014
    Messages : 11
    Points : 14
    Points
    14
    Par défaut
    Par contre j'ai une autre piste : Les hugepages.
    quand je tape la commande : grep huge /proc/meminfo, j'obtiens :
    AnonHugePages: 331776 kB
    HugePages_Total: 0
    HugePages_Free: 0
    HugePages_Rsvd: 0
    HugePages_Surp: 0
    HugepagesSize : 2048 kB

    Jusqu'à présent je pensais que je n'utilisais pas de HugePages car le Total était de 0. Mais je viens de lire cela : "AnonHugePages: Total number of anonymous huge pages."
    Je n'ai pas eu le temps de faire de tests supplémentaire mais il semblerait que la valeur de AnonHugePages augmente au cours du programme. Il se peut donc que le programme utilise des huge pages même si HugePages_Total = 0. Du coup, les variations du temps d'exécution observé viendrait du fait qu'il utilise ou non des huge pages. Réponse au prochain épisode

  20. #20
    Membre à l'essai
    Homme Profil pro
    Étudiant
    Inscrit en
    Avril 2014
    Messages
    11
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 30
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Avril 2014
    Messages : 11
    Points : 14
    Points
    14
    Par défaut
    Le problème venait bien de la quantité de AnonHugePages allouées, c'est-à-dire la quantité de grandes pages (2MB au lieu des 4kB habituels) allouées par le système.

    Quand la mémoire était allouée, malloc appelait l'appel système brk()pour augmenter la pile. Cependant la quantité de HugePages, allouée n'était pas constante (peut-être à cause de la fragmentation ).

    Un moyen de contourner ce problème est d'allouer la mémoire avec l'appel système mmap. Dans ce cas là, le système alloue automatiquement des Hugepages.

    Encore merci à tous ceux qui m'ont conseillé

+ 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