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 :

Problème de lenteur avec un vecteur de vecteur


Sujet :

C++

  1. #1
    Membre éclairé

    Homme Profil pro
    Inscrit en
    Octobre 2008
    Messages
    426
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Puy de Dôme (Auvergne)

    Informations forums :
    Inscription : Octobre 2008
    Messages : 426
    Points : 827
    Points
    827
    Par défaut Problème de lenteur avec un vecteur de vecteur
    Salut à tous,

    J'ai récemment créé un classe ( en natif ) dont l'un des membres donnée est un tableau 2d du type vector<vector<double> >. L'une des fonction membre fais l'addition membre à membre de trois de ces tableaux et là : c'est horriblement lent!

    Dans mes essais, le tableau fais environ 500*400, et le traitement de celui-ci prend facilement0,2 à 0,3 secondes!!. Et il faut réitérer le calcul des centaines et des milliers de fois! ( C'est une simulation de diffusion osmotique en milieux 2d, pour chaque 'pixel' on détermine quelle quantité de 'produit' est ( ou pas ) transférée aux 'pixels' voisins )

    Puisque le PC sur lequel je fais tourner ce code est capable de faire tourner un simulateur de vol en temps réel, j'imagine que le problème ne vient pas du PC mais du programmeur!

    Quels sont les "trucs et astuces" pour pouvoir demander au PC de traiter plus rapidement une partie du code. J'ai lu sur certains posts des histoires de "cache" : un développer de jeux disait qu'il faut savoir l'utiliser pour que ça tourne vite, mais ça dépasse mes connaissances. Pouvez-vous me mettre sur la ou les bonnes pistes?

    PS : Le PC en question est monoprocesseur, inutile donc de me parler de parallélisation

  2. #2
    screetch
    Invité(e)
    Par défaut
    un vecteur de vecteur est horriblement lent.
    montre le code qui fait l'addition pour avoir plus d'idées.
    Dans vector il y a une fonction "resize" qui pourrait t'aider aussi:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    vector<vector<double> > result;
    result.resize(input.size());
    for(int i = 0; i < input1.size(); ++i)
    {
      result[i].resize(input[i].size());
      for(int j = 0; j < input[i].size(); ++j)
      {
        result[i][j] = input[i][j] + input2[i][j];
      }
    }
    c'est deja un début.

  3. #3
    Membre éclairé

    Homme Profil pro
    Inscrit en
    Octobre 2008
    Messages
    426
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Puy de Dôme (Auvergne)

    Informations forums :
    Inscription : Octobre 2008
    Messages : 426
    Points : 827
    Points
    827
    Par défaut
    un vecteur de vecteur est horriblement lent
    Alors que puis-je utiliser à la place d'un vecteur de vecteur?
    Pourquoi 'resize'?

    Ca fait plusieurs jours que je met au point et que j'optimise mon code ( ce morceau n'en est qu'une partie ). Mais j'ai commis une grosse erreur de débutant : c'est de ne pas faire un template dès le début ! Je me rends compte en testant mon programme que je n'ai pas forcément besoin de la précision d'un double. Je réécris actuellement ma class sous forme de template

    Voici des extraits : la déclaration de mon class template et la définition de la fonction Diffuse:
    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
     
    template <typename T, typename U> class Terrain
    {
    protected:
    	unsigned int Width;
    	unsigned int Height;
    	std::vector<std::vector<T> > Sol;
    	T CoefficientDeDiffusion;
    	T CoefficientDEvaporation;
    public:
    	Terrain ( const unsigned int l, const unsigned int h );
    	Terrain ( const Terrain<T,U>& ter );
    	~Terrain ();
    	Terrain<T,U>& operator = ( Terrain<T,U> &ter );
     
    	void affiche () const;
     
    	void SetSol ( const unsigned int l, const unsigned int h, T valeur );
    	void SetCoefficientDeDiffusion ( T coeffDiff );
    	void SetCoefficientDEvaporation ( T coeffEvap );
     
    	void Diffuse ( U dT );
    };
     
    template <typename T, typename U> void Terrain<T,U>::Diffuse ( U dT )
    {
    	std::vector<std::vector<T> > flux;
    	std::vector<std::vector<T> > pertes;
    	T courant;			//Valeur du pixel courant
    	T vh, vb, vg, vd;	//Valeurs des pixels voisins
    	T p, pTempo;			//Variables auxiliaires
    	const T zero = static_cast<T>( 0 );
     
    	//flux et pertes sont initialisée pour faire sx de large et sy de haut
    	for ( unsigned int y = 0; y < Height; y++ )
    	{
    		flux.push_back ( std::vector<T> ( Width,  zero ) );
    		pertes.push_back ( std::vector<T> ( Width,  zero ) );
    	}
     
    	//Calcule de diffusion
    	for ( unsigned int y = 0; y < Height; ++y )
    	{
    		for ( unsigned int x = 0; x < Width; ++x )
    		{
    			vh = zero;	vb = zero;	vg = zero;	vd = zero;	p = zero;
    			courant = Sol[y][x];
     
    			//Calcul de flux : ce que gagne chaques pixels voisins
    			if ( y )			//Si on est pas à la première ligne
    			{
    				vh = Sol[y-1][x];
    				if ( courant > vh )
    				{
    					pTempo = dT * CoefficientDeDiffusion * ( courant - vh );
    					flux[y-1][x] = pTempo;
    					p += pTempo;
    				}
    			}
    			if ( y < Height-1 )	//Si on est pas à la dernière ligne
    			{
    				vb = Sol[y+1][x];
    				if ( courant > vb )
    				{
    					pTempo = dT * CoefficientDeDiffusion * ( courant - vb );
    					flux[y+1][x] = pTempo;
    					p += pTempo;
    				}
    			}
    			if ( x )			//Si on est pas à la première colonne
    			{
    				vg = Sol[y][x-1];
    				if ( courant > vg )
    				{
    					pTempo = dT * CoefficientDeDiffusion * ( courant - vg );
    					flux[y][x-1] = pTempo;
    					p += pTempo;
    				}
    			}
    			if ( x < Width-1 )	//Si on est pas à la dernière colonne
    			{
    				vd = Sol[y][x+1];
    				if ( courant > vd )
    				{
    					pTempo = dT * CoefficientDeDiffusion * ( courant - vd );
    					flux[y][x+1] = pTempo;
    					p += pTempo;
    				}
    			}
     
    			//Calcul de pertes : ce que perd ce pixel
    			pertes[y][x] = p + dT * CoefficientDEvaporation;
    		}
    	}
     
    	//Calcul final : On additionne au tableau d'origine le tableau des flux
    	//				et on y soustrait celui des pertes
    	for ( unsigned int y = 0; y < Height; ++y )
    		for ( unsigned int x = 0; x < Width; ++x )
    			Sol[y][x] += flux[y][x] - pertes[y][x];
    }

  4. #4
    Membre expérimenté
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Mars 2011
    Messages
    576
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mars 2011
    Messages : 576
    Points : 1 528
    Points
    1 528
    Par défaut
    Salut,

    Première chose, débrouille toi pour organiser tes données de manière à ce qu'elles soit à la suite dans la mémoire. Par exemple, n'utilise qu'un seul tableau comme ceci:

    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
     
    int largeur = 500;
    int hauteur = 800;
    std::vector<double> tableau(largeur*hauteur);
    // acceder a la valeur (i, j):
    tableau[j * largeur + i];
     
    // parcourir tous les points:
    // Attention, la boucle du i doit être à l'interieur, de manière a ce que
    // lorsque tu parcour ton tableau, tu ne fasse pas de "saut" dans la mémoire
    for ( int j = 0; j < hauteur; ++j )
    {
      for ( int i = 0; i < largeur; ++i )
      {
         tableau[j * largeur + i];
      }
    }
    C'est moins claire, mais diablement plus rapide
    La perfection est atteinte, non pas lorsqu’il n’y a plus rien à ajouter, mais lorsqu’il n’y a plus rien à retirer. - Antoine de Saint-Exupéry

  5. #5
    screetch
    Invité(e)
    Par défaut
    dans ton code:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    	for ( unsigned int y = 0; y < Height; y++ )
    	{
    		flux.push_back ( std::vector<T> ( Width,  zero ) );
    		pertes.push_back ( std::vector<T> ( Width,  zero ) );
    	}
    je parie c'est ca qui est lent
    push_back peut réallouer le vector
    en réallouant le vector il doit recopier tous les vector qu'il contient
    c'est exactement pour ca que vector<vector> c'est nul niveau perfs
    resize visait justement a éviter ca.

    la différence entre float et double se fera peut etre meme pas sentir... la regle si on veut améliorer les perfs est de voir ou l'on passe du temps.
    si tu ne sais pas ce qu'est un profiler:
    * soit c'est le bon moment pour apprendre
    * soit tu mets des timer dans ton code pour calculer combien de temps prennent les opérations

    sinon tu optimises a l'aveugle et ca c'est un peu naze

  6. #6
    Membre éclairé

    Homme Profil pro
    Inscrit en
    Octobre 2008
    Messages
    426
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Puy de Dôme (Auvergne)

    Informations forums :
    Inscription : Octobre 2008
    Messages : 426
    Points : 827
    Points
    827
    Par défaut
    Merci pour les réponses screetch et pyros

    @Pyros : Je vais tester cette façon de procéder en tenant bien compte du fait que
    la boucle du i doit être à l'interieur, de manière a ce que lorsque tu parcour ton tableau, tu ne fasse pas de "saut" dans la mémoire
    , ça permettra de virer les "push_back ( std::vector<T> ( Width, zero )" sur lesquels Screetch attire mon attention

    @Screetch : En effet, je ne connais pas les "profilers" ( C'est le problème quand on est autodidacte dans un domaine, on ne connais que ce dont on a eu besoin ( moi je suis ingénieur en mécanique à la base, et il y a 15 ans dans mon école, la formation en informatique était assez limitée : quelques heures de pascal tout au plus!!! ) ). Je vais tester la méthode des timers, et regarder ce qu'est un "profiler" dès que j'en ai le temps.

    Merci encore à vous deux, donc.

    Je coche ce post comme résolu, mais si quelqu'un a d'autres piste, qu'il n'hésite pas.

  7. #7
    r0d
    r0d est déconnecté
    Expert éminent

    Homme Profil pro
    tech lead c++ linux
    Inscrit en
    Août 2004
    Messages
    4 262
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : tech lead c++ linux

    Informations forums :
    Inscription : Août 2004
    Messages : 4 262
    Points : 6 680
    Points
    6 680
    Billets dans le blog
    2
    Par défaut
    Effectivement, la différence entre double et float ne se fera pas sentir.

    Quelques conseils basés sur mon expérience personnelle:
    - Comme il a été dit: pas de vector< vector >, mais un vector à une dimension.
    - Je conseille d'encapsuler ce vector dans une classe "matrice". Voir par exemple cette classe.
    - Fournir un itérateur qui te permet un parcours linéaire du vecteur. En effet, si tu dois parcourrir le tableau en entier, utiliser un itérateur sera beaucoup, mais vraiment beaucoup (et encore plus dans ce cas particulier) plus rapide que l'accès par indice.
    « L'effort par lequel toute chose tend à persévérer dans son être n'est rien de plus que l'essence actuelle de cette chose. »
    Spinoza — Éthique III, Proposition VII

  8. #8
    Membre expérimenté
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Mars 2011
    Messages
    576
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mars 2011
    Messages : 576
    Points : 1 528
    Points
    1 528
    Par défaut
    Y'a-t-il vraiment une différence entre parcourir un vector avec un iterator plutôt qu'avec un indice ?

    Pour une liste chainée par exemple, ok, car il faut repartir à chaque fois du début pour accéder au ième élément, mais pour un vecteur, v[i] = *(&v[0] + i), donc pour moi on économise juste une addition en passant par un itérateur... Me cacherait-on des optimisations que j'ignore ?

    De plus, je trouve la syntaxe des itérateurs lourde pour un simple parcour de vecteur, et confuse, surtout pour un débutant, mais ce n'est qu'une opinion personnelle...
    La perfection est atteinte, non pas lorsqu’il n’y a plus rien à ajouter, mais lorsqu’il n’y a plus rien à retirer. - Antoine de Saint-Exupéry

  9. #9
    screetch
    Invité(e)
    Par défaut
    ca n'est pas vraiment plus rapide, c'est surtout plus sécurisé (un pointeur est non borné, des itérateurs, surtout en debug, ont des bornes et peuvent avertir en cas d'écrasement mémoire)
    c'est du temps de gagné au final

  10. #10
    r0d
    r0d est déconnecté
    Expert éminent

    Homme Profil pro
    tech lead c++ linux
    Inscrit en
    Août 2004
    Messages
    4 262
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : tech lead c++ linux

    Informations forums :
    Inscription : Août 2004
    Messages : 4 262
    Points : 6 680
    Points
    6 680
    Billets dans le blog
    2
    Par défaut
    Dans le cas présent, on gagne au moins une addition et une mutliplication( tableau[j * largeur + i]; )
    Je ne retrouve pas le lien (il me semblait que c'était dans les gotw mais visiblement non car je ne trouve pas), mais j'avais lu qu'en passant par un indice il y a une perte au niveau de l'adressage ou je ne sais plus trop. Ou peut-être au niveau de la comparaison de fin de boucle.
    « L'effort par lequel toute chose tend à persévérer dans son être n'est rien de plus que l'essence actuelle de cette chose. »
    Spinoza — Éthique III, Proposition VII

  11. #11
    Membre éclairé

    Homme Profil pro
    Inscrit en
    Octobre 2008
    Messages
    426
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Puy de Dôme (Auvergne)

    Informations forums :
    Inscription : Octobre 2008
    Messages : 426
    Points : 827
    Points
    827
    Par défaut
    Voici une version optimisée, d'après vos conseils, de ma fonction 'Diffuse' et de la classe dont elle est membre :
    ici : 'Zéro' est passé membre de la classe et 'Threshold', qui est un seuil réglable, aussi.
    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
    template <typename T, typename U> class Terrain
    {
    	//type T : type des valeurs traitées dans la matrice
    	//type U : type utilisé pour le deltaT
    protected:
     
    	unsigned int Width;		//Largeur du terrain
    	unsigned int Height;	//Hauteur du terrain
    	unsigned int Size;		//Taille du terrain = Width*Height
    	std::vector<T> Surface;	//Surface sur laquel est simulée la diffision
    	U CoefficientDeDiffusion;
    	U CoefficientDEvaporation;
    	T Threshold;			//Seuil en dessous duquel on considère que la valeur est nulle
    	T Zéro;					//Le zéro du type T
     
    public:
     
    	Terrain ( const unsigned int largeur, const unsigned int hauteur );
    	~Terrain ();
     
    	unsigned int GetWidth ();
    	unsigned int GetHeight ();
     
    	void SetCoefficientDeDiffusion ( U coeffDiff );
    	U GetCoefficientDeDiffusion () ;
    	void SetCoefficientDEvaporation ( U coeffEvap );
    	U GetCoefficientDEvaporation ();
    	void SetThreshold ( T seuil );
    	T GetThreshold ();
     
    	unsigned int GetX ( unsigned int index );
    	unsigned int GetY ( unsigned int index );
     
    	void SetValue ( unsigned int posX, unsigned int posY, T valeur );
    	T GetValue ( unsigned int posX, unsigned int posY );
    	void Reset ();
     
    	void affiche () const;	//Affichage en mode console
    	void affiche ( unsigned char* image, int tailleLigne );	//Affichage dans bitmap
     
    	void Diffuse ( U dT );
    };
     
    template <typename T, typename U> void Terrain<T,U>::Diffuse ( U dT )
    {
    	//Matrices utilisées pour le calcul
    	std::vector<T> gain ( Size, Zéro );//Matrice des gains de chaques cases
    	std::vector<T> perte ( Size, Zéro );//Matrice des pertes de chaques cases
     
    	//Variables de la boucle de calcul principale ( Calcul des matrices 'gain' et 'perte' )
    	T fluxHaut, fluxBas, fluxDroite, fluxGauche, fluxEvap;
    	unsigned int index;	//Index de la case voisine ( Haute, Basse, Gauche ou Droite )
    	T delta;
    	T valeurCourante;
     
    	//Précalcul de valeurs constantes à cette fonction
    	const T coeffDiffDeltaT = static_cast<T> ( CoefficientDeDiffusion * dT );
    	const T coeffEvapDeltaT = static_cast<T> ( CoefficientDEvaporation * dT );
     
    	//Calcul des matrices 'gain' et 'perte'
    	for ( unsigned int indexCourant = 0; indexCourant < Size; indexCourant++ )
    	{
    		//Initialisation
    		valeurCourante = Surface[ indexCourant ];
     
    		if ( valeurCourante != Zéro )	//Si la valeur courante est non nulle on calcule sinon on passe le tour
    		{
    			//Initialisation des variables de la boucle
    			fluxHaut = Zéro;	fluxBas = Zéro;	fluxGauche = Zéro;	fluxDroite = Zéro;	fluxEvap = Zéro;
     
    			//Traitement du pixel Haut :
    			if ( indexCourant >= Width )					//Si on est pas à la première ligne :
    			{
    				index = indexCourant - Width;
    				delta = valeurCourante - Surface [ index ];
    				if ( delta > Zéro )
    				{
    					fluxHaut = coeffDiffDeltaT * delta;
    					gain [ index ] += fluxHaut;
    				}
    			}
    			//Traitement du pixel Bas :
    			if ( ( indexCourant / Width ) < ( Height - 1 ) )//Si on est pas à la dernière ligne :
    			{
    				index = indexCourant + Width;
    				delta = valeurCourante - Surface [ index ];
    				if ( delta > Zéro  )
    				{
    					fluxBas = coeffDiffDeltaT * delta;
    					gain[ index ] += fluxBas;
    				}
    			}
    			//Traitement du pixel Gauche :
    			if ( indexCourant % Width )						//Si on est pas à la première colonne :
    			{
    				index = indexCourant - 1;
    				delta = valeurCourante - Surface [ index ];
    				if ( delta > Zéro )
    				{
    					fluxGauche = coeffDiffDeltaT * delta;
    					gain[ index ] += fluxGauche;
    				}
    			}
    			//Traitement du pixel Droite :
    			if ( ( indexCourant % Width ) < ( Width - 1 ) )//Si on est pas à la dernière colonne :
    			{
    				index = indexCourant + 1;
    				delta = valeurCourante - Surface [ index ];
    				if ( delta > Zéro )
    				{
    					fluxDroite = coeffDiffDeltaT * delta;
    					gain[ index ] += fluxDroite;
    				}
    			}
     
    			fluxEvap = coeffEvapDeltaT * valeurCourante;
    			perte[ indexCourant ] = fluxHaut + fluxBas + fluxDroite + fluxGauche + fluxEvap;
    		}
    	}
     
    	//Synthèse des trois matrices : Surface, gain et perte
    	for ( unsigned int indexCourant = 0; indexCourant < Size; indexCourant++ )
    		Surface[ indexCourant ] += gain[ indexCourant ] - perte[ indexCourant ];
     
    	//On annule les pixels de trop faible valeur
    	if ( Threshold != 0 )
    		for ( unsigned int indexCourant = 0; indexCourant < Size; indexCourant++ )
    			if ( Surface [ indexCourant ] < Threshold )
    				Surface [ indexCourant ] = Zéro;
    }
    J'ai donc utilisé des vectors à une seule dimension, plutot que des vectors de vectors, et là c'est vraiment beaucoup plus rapide. ( La transition de tableaux 2d à des tableaux 1d a demandé un certaine attention pour convertir les indices (x,y) en indices 'index' correctement dans un sens comme dans l'autre! )
    J'ai également optimisé l'utilisation des variables locales et précalculé certaines valeurs constantes à une boucle pour éviter de les recalculer à chaque itération de celle-ci
    Je n'ai pas utilisé d'itérateurs pour le parcours des vectors car cet algorithme ne s'y prête pas.
    Le Fait d'attribuer un type différent aux valeurs des tableau, et aux coefficients et à deltaT, est délibéré : ça me réserve la possibilité, éventuelle (en adaptant l'algo à peu de frais ), de créer des tableaux d'entiers ( au lieu de réels ) dans l'espoir de gagner encore un peu en performances si nécessaire.

    Encore merci à tous pour votre assistance...

  12. #12
    r0d
    r0d est déconnecté
    Expert éminent

    Homme Profil pro
    tech lead c++ linux
    Inscrit en
    Août 2004
    Messages
    4 262
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : tech lead c++ linux

    Informations forums :
    Inscription : Août 2004
    Messages : 4 262
    Points : 6 680
    Points
    6 680
    Billets dans le blog
    2
    Par défaut
    Ton code est très propre

    Juste quelques petits conseils, pas hyper importants, et peut-être pas très pertinents ici, mais toujours intéressants de souligner.

    1. Utilisation de référence constantes pour les paramètres de fonctions.

    Ton code:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    void SetCoefficientDeDiffusion ( U coeffDiff );
    void SetCoefficientDEvaporation ( U coeffEvap );
    void SetThreshold ( T seuil );
    Il est conseillé de faire plutôt:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    void SetCoefficientDeDiffusion ( const U & coeffDiff );
    void SetCoefficientDEvaporation ( const U & coeffEvap );
    void SetThreshold ( const T & seuil );
    Dans le cas de types atomiques (int, float, double, ...), ça n'aura aucune incidence. Mais si jamais, un jour, tu veux que ta matrice contienne un type composé (une structure, une classe), alors en passant une référence constante tu éviteras une copie (bon en réalité c'est un peu plus compliqué car le compilateur fera lui-même l'optimisation s'il le peut, mais il ne le peut pas toujours, en particulier si le type n'est pas un POD).
    Mais surtout, c'est une bonne habitude à prendre que d'utiliser des références constantes pour les paramètres de tes fonctions:
    - en terme de rapidité d'exécution: au pire, ce sera pareil qu'un passage par valeur
    - le passage de paramètre par référence constante apporte une évidente plus-value en terme de sémantique. En gros, cela revient à dire à celui qui va lire le code (souvent nous-même): ce paramètre ne doit pas être modifié. Et puis ça permet de respecter un principe fort du c++: la const conformité.

    2. const correctness (ou const conformité):

    Ton code:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    unsigned int GetWidth ();
    unsigned int GetHeight ();
     
    U GetCoefficientDeDiffusion ();
    U GetCoefficientDEvaporation ();
    T GetThreshold ();
    Le même code qui respecte la const conformité:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    unsigned int GetWidth () const;
    unsigned int GetHeight () const;
     
    U GetCoefficientDeDiffusion () const;
    U GetCoefficientDEvaporation () const;
    T GetThreshold () const;
    En gros, l'idée c'est que toutes les fonctions qui ne sont pas sensées modifier l'instance appelante doivent être déclarées const.
    Les avantages de cette pratique (liste non exhaustive):
    - on s'interdit ainsi certaines erreurs bêtes que l'on pourrait faire dans le code de la fonction. Lorsqu'on crée une fonction, on a une idée de ce que doit faire cette fonction. Si, en fonction du contexte global du programme, on estime que cette fonction ne doit pas modifier l'instance appelante, mais qu'ensuite on oublie que cette fonction avait été pensée ainsi, on risque de la modifier, ce qui pourrait engendrer des comportements non prévus dans d'autres endroits du programme. La constance de la fonction nous rappelera que cette fonction a été pensée ainsi.
    - on ajoute une plus-value sémantique (encore, mais oui, c'est important). La personne qui va lire ce code (souvent celui qui l'a écrit, mais plus tard, une fois qu'il a oublié les détails d'implémentation) saura qu'il peut appeler cette fonction sans crainte d'effet de bord.


    Pour résumer, si l'ensemble de ton code respecte la const conformité, il gagnera beaucoup en stabilité et en facilité de maintenance. Deux avantages qui ne sont pas anodins.


    3. Je ne pense pas que ce soit pertinent ici, mais souvent il est préférable que nos fonctions retournent des références, plutôt que des valeurs. Par exemple, au lieu de:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    T GetThreshold () const;
    Faire plutôt:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    const T & GetThreshold () const; //le const du début c'est pour préserver la const conformité
    Les avantages:
    - on évite une copie, donc dans certains cas on peut gagner en performances (si type non POD, typiquement)
    - on s'assure de la validité du paramètre retourné: pour pouvoir retourner une référence, il faut que le paramètre retourné soit membre de la classe (il y a quelques exceptions que j'ignorerai ici, car pas intéressant). Si l'on renvoie une référence sur une variable qui n'existe que dans le scope de la fonction, le compilateur va nous jeter violemment (au moins un gros warning), ce qui nous oblige à ce que notre code soit cohérent.

    Attention, le retour de référence (par opposition à retour de valeur) n'est pas systématiquement une bonne solution: souvent il vaut mieux renvoyer une copie.
    « L'effort par lequel toute chose tend à persévérer dans son être n'est rien de plus que l'essence actuelle de cette chose. »
    Spinoza — Éthique III, Proposition VII

  13. #13
    r0d
    r0d est déconnecté
    Expert éminent

    Homme Profil pro
    tech lead c++ linux
    Inscrit en
    Août 2004
    Messages
    4 262
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : tech lead c++ linux

    Informations forums :
    Inscription : Août 2004
    Messages : 4 262
    Points : 6 680
    Points
    6 680
    Billets dans le blog
    2
    Par défaut
    Juste un dernier petit truc, concernant le code suivant (puisqu'il s'agit d'optimisation):
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    for ( unsigned int indexCourant = 0; indexCourant < Size; indexCourant++ )
    Lorsque j'ai appris le c++, on disait qu'on gagnait en performance en faisant plutôt:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    for ( unsigned int indexCourant = 0; indexCourant < Size; ++indexCourant )
    En effet, l'opérateur de post-incrémentation (i++) effectue plus de calculs que l'opérateur de pré-incrémentation (++i).
    Mais je ne sais pas si aujourd'hui cette remarque est encore valable. En effet, les compilateurs sont de plus en plus "intelligents", et ça ne m'étonnerais pas que cela soit optimisé par le compilo.

    Même remarque pour la condition d'arrêt. Théoriquement, il vaut mieux un != qu'un <. Le != étant généralement plus rapide, et il ne faut pas oublier que la condition d'arrêt est appelée à chaque itération. Mais après, dans la pratique, j'imagine que les compilateurs récents font des optimisations qui rendent caduque cette remarque.
    « L'effort par lequel toute chose tend à persévérer dans son être n'est rien de plus que l'essence actuelle de cette chose. »
    Spinoza — Éthique III, Proposition VII

  14. #14
    Membre éclairé

    Homme Profil pro
    Inscrit en
    Octobre 2008
    Messages
    426
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Puy de Dôme (Auvergne)

    Informations forums :
    Inscription : Octobre 2008
    Messages : 426
    Points : 827
    Points
    827
    Par défaut
    @ r0d : Merci pour ces précisions, elles ont été lues et comprises. Les modifications sont faites... ( les const et les ++ )

    Ton explication m'a également donné une idée pour une petite optimisation mineure : J'ai modifié le membre donnée
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    T Zéro; //Le zéro du type T
    en
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    const T Zéro; //Le zéro du type T
    puisque, en général, zéro est plutôt du genre constant lui aussi

    PS : Pour ceux qui comme moi ne connaissaient pas le terme POD , cliquez là.

  15. #15
    screetch
    Invité(e)
    Par défaut
    le ++ en pré-incrémentation comme r0d le dit est une bonne habitude a prendre.
    Sur un entier ca n'a pas d'influence
    Sur un type itérateur (l'autre grand cas de figure) un opérateur de pré-incrémentation incrémente le paramètre tandis qu'un opérateur de post-incrémentation fait une copie et incrémente.
    Dans la mesure ou la copie est inutile, autant faire de la pré-incrémentation (si ca ne tenait qu'a moi, la post-incrémentation serait supprimée, ca éviterait en plus les petits malins qui abusent des effets de bord et des lignes a la mord-moi-le-noeud)
    les compilateurs l'optimisent sans doute mais je vois mal l'utilité, c'est juste une bonne habitude.

    Pour le reste, question subsidiaire: y a-t'il différence entre un double et un float maintenant en terme de vitesse?

  16. #16
    r0d
    r0d est déconnecté
    Expert éminent

    Homme Profil pro
    tech lead c++ linux
    Inscrit en
    Août 2004
    Messages
    4 262
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : tech lead c++ linux

    Informations forums :
    Inscription : Août 2004
    Messages : 4 262
    Points : 6 680
    Points
    6 680
    Billets dans le blog
    2
    Par défaut
    Citation Envoyé par screetch Voir le message
    Pour le reste, question subsidiaire: y a-t'il différence entre un double et un float maintenant en terme de vitesse?
    Ben en fait, c'est une question ouverte.
    le gotw que j'ai cité plus haut dit que non.
    Cependant, j'ai eu l'occasion de discuter avec un gars qui bosse sur les processeurs intel, en argentine, et il me disait que ça dépend tellement du couple compilo/processeur qu'on ne peut pas y répondre comme ça de façon générale. Si ça intéresse quelqu'un, il y a une discussion sur ce sujet sur la newsgroup cppba (cpp buenos aires), avec le gars en question. Si ça intéresse quelqu'un donc, dites-moi je vous retrouverai le lien. C'est en espagnol par contre.
    « L'effort par lequel toute chose tend à persévérer dans son être n'est rien de plus que l'essence actuelle de cette chose. »
    Spinoza — Éthique III, Proposition VII

  17. #17
    r0d
    r0d est déconnecté
    Expert éminent

    Homme Profil pro
    tech lead c++ linux
    Inscrit en
    Août 2004
    Messages
    4 262
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : tech lead c++ linux

    Informations forums :
    Inscription : Août 2004
    Messages : 4 262
    Points : 6 680
    Points
    6 680
    Billets dans le blog
    2
    Par défaut
    Concernant la variable zero, j'aurais un petit mot à dire. Désolé, je suis un peu lourd mais c'est un sujet qui m'intéresse beaucoup et j'ai passé beaucoup de temps à y réfléchir et a implémenter ce type de classe matrice.

    Donc en fait, plutôt que zero, je pense qu'il est préférable d'utiliser element_neutre (ou neutral_element en anglais). C'est, d'un point de vue général, pour mieux "coller" avec le jargon de l'algèbre linéaire; et concrètement, c'est parce que si un jour tu veux mettre autre chose que des numéros (int, double, float...), le zero n'aura plus de sens.

    De plus, cette valeur devrait être fournie par l'utilisateur (typiquement en paramètre du constructeur), car on ne peut pas savoir quel sera l'élément neutre si on ne connais pas le type des éléments stockés dans la matrice (ce qui est normalement le cas lorsqu'on fait une classe template).

    Bon, dans ce cas précis, toi tu sais que tu n'auras pas des choses bizzares, donc ma remarque n'est pas vraiment applicable dans ton cas. C'était juste pour "élever le débat"
    « L'effort par lequel toute chose tend à persévérer dans son être n'est rien de plus que l'essence actuelle de cette chose. »
    Spinoza — Éthique III, Proposition VII

  18. #18
    screetch
    Invité(e)
    Par défaut
    Citation Envoyé par r0d Voir le message
    Ben en fait, c'est une question ouverte.
    le gotw que j'ai cité plus haut dit que non.
    Oui je me suis mal exprimé, je voulais dire sur ce cas précis, pas en général
    Comme bertry avait fait les deux implémentations, je me demandais quels ont été ses résultats

  19. #19
    Membre éclairé

    Homme Profil pro
    Inscrit en
    Octobre 2008
    Messages
    426
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Puy de Dôme (Auvergne)

    Informations forums :
    Inscription : Octobre 2008
    Messages : 426
    Points : 827
    Points
    827
    Par défaut
    Citation Envoyé par screetch Voir le message
    Oui je me suis mal exprimé, je voulais dire sur ce cas précis, pas en général
    Comme bertry avait fait les deux implémentations, je me demandais quels ont été ses résultats
    Pas d'impatience! Voici le résultat :
    Tout d'abord le petit programme de test :
    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
    #include <windows.h>
    #include "Terrain.h"	//La fameuse class 
    using namespace std;
     
    void main ()
    {
    	const unsigned int sx = 2000;  //Dimensions de la matrice
    	const unsigned int sy = 2000;
     
    	long int beforeD, beforeF;  //Variables pour la mesure du temps d'éxécution
    	long int afterD, afterF;
    	long int tempsD, tempsF;
     
            //Instanciation des deux terrains
    	Terrain<double, double> TD ( sx, sy );
    	Terrain<float , float > TF ( sx, sy );
     
            //Initialisation des coeff des deux terrains
    	TD.SetCoefficientDeDiffusion ( 0.5 );
    	TF.SetCoefficientDeDiffusion ( 0.5f );
    	TD.SetCoefficientDEvaporation ( 0.05 );
    	TF.SetCoefficientDEvaporation ( 0.05f );
     
            //Initialisation des valeurs des matrices des deux terrains
    	for ( unsigned int i = 1; i < sy-1; ++i )
    	{
    		TD.SetValue ( i, i, 1. );
    		TD.SetValue ( sx-i, sy-i, 1. );
    		TF.SetValue ( i, i, 1.f );
    		TF.SetValue ( sx-i, sy-i, 1.f );
    	}
     
            //Mesure des temps d'éxécution
    	beforeD = GetTickCount();
    	TD.Diffuse ( 0.1 );
    	afterD = GetTickCount();
    	beforeF = GetTickCount();
    	TF.Diffuse ( 0.1f );
    	afterF = GetTickCount();
     
    	tempsD = afterD - beforeD;
    	tempsF = afterF - beforeF;
     
    	cout << "tempsD = " << tempsD << endl;
    	cout << "tempsF = " << tempsF << endl;
     
    	system ("pause");
    }
    Et le résultat ( sous WindowsXP compilé avec Visual C++ en mode Release, µP : AMD Sempron ), sur plusieurs essais les valeurs les plus fréquentes sont :

    Pour la classe en double : tempsD = 203ms
    Pour la classe en float : tempsF = 156ms

    Il semble donc que je gagne 25% de temps en passant des double aux float... C'est pas rien quand même!

  20. #20
    Membre éclairé

    Homme Profil pro
    Inscrit en
    Octobre 2008
    Messages
    426
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Puy de Dôme (Auvergne)

    Informations forums :
    Inscription : Octobre 2008
    Messages : 426
    Points : 827
    Points
    827
    Par défaut
    Citation Envoyé par r0d Voir le message
    Donc en fait, plutôt que zero, je pense qu'il est préférable d'utiliser element_neutre (ou neutral_element en anglais). C'est, d'un point de vue général, pour mieux "coller" avec le jargon de l'algèbre linéaire; et concrètement, c'est parce que si un jour tu veux mettre autre chose que des numéros (int, double, float...), le zero n'aura plus de sens.
    @r0d : Tu as raison, mais dans ce cas précis, la matrice ne peut contenir que des valeurs atomiques ( double, float voire éventuellement, en spécialisant le template, int ) : donc je conserve mon Zéro ( mais je garde ta remarque en mémoire pour de futurs travaux )

+ Répondre à la discussion
Cette discussion est résolue.
Page 1 sur 2 12 DernièreDernière

Discussions similaires

  1. Réponses: 5
    Dernier message: 18/05/2010, 18h12
  2. [WD11][E-07]Problème de lenteur avec le driver odbc
    Par law56100 dans le forum HyperFileSQL
    Réponses: 0
    Dernier message: 26/05/2009, 17h50
  3. Problème de lenteur avec requêtes MySql en réseau local
    Par marcootz dans le forum Requêtes
    Réponses: 2
    Dernier message: 08/11/2007, 14h07
  4. problème de lenteur avec BO
    Par darwini dans le forum Débuter
    Réponses: 2
    Dernier message: 13/04/2007, 14h08
  5. Problème de lenteur avec 2 sous-formulaires
    Par picatchou dans le forum Access
    Réponses: 1
    Dernier message: 29/01/2007, 08h48

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