Partage
  • Partager sur Facebook
  • Partager sur Twitter

Passage pas référence ou copie d'une variable ?

Sujet résolu
    18 février 2018 à 9:38:55

    Bonjour,

    j'ai un programme qui comporte de nombreuses fonctions, et dont les arguments peuvent être assez importants. Ma question est la suivante : est-il plus rapide de passer ces arguments par référence ou en copiant la variable ? Est-ce plus propre ?

    Merci d'avance !

    • Partager sur Facebook
    • Partager sur Twitter
    Merci !
      18 février 2018 à 11:26:05

      Bonjour Gryfbane, 

      Passer par référence est forcement plus rapide car les étapes de création de variable et de copie ne sont pas réalisés, ceci dit ce temps est je pense negligable suivant ce que tu comptes passer par valeur (= par copie) .. copier un int n'est pas aussi long que de copier un tableau ou une texture par exemple. Donc passer par référence semble aussi bien "optionnelle" que "obligatoire" selon les cas.

      Quoi qu'il en soit il faut garder à l'esprit que lorsque que tu utilises le passage par valeur, la valeur est copié et que donc tu peux faire toutes les modifications que tu veux dessus dans la fonction cela ne modifiera pas la variable passé en argument de ta fonction, car la variable sera locale, interne à la fonction .. alors que avec une référence ce n'est pas le cas, à moins de la declarer comme constante dans ta fonction.  

      Que l'on me corrige si j'ai dis quelque chose de faux ou d'inexcate :p

      -
      Edité par Miscell 18 février 2018 à 11:34:27

      • Partager sur Facebook
      • Partager sur Twitter
        18 février 2018 à 11:45:39

        Le passage du paramètre est une chose, mais il y a ensuite l'utilisation de la variable dans la fonction appelée. Si on a passé une référence, il y aura des indirections.

        Dons si on s'intéresse vraiment au problème d'optimisation, il va falloir y regarder de beaucoup plus près : le contexte, ce qu'on fait avec les variables, des mesures de performances.

        En général on a de bonnes raisons de passer par référence, si les données qu'on passe sont des objets avec sémantique d'entité, parce que faire une copie n'a même pas de sens.

        • Partager sur Facebook
        • Partager sur Twitter
          18 février 2018 à 11:50:06

          Hello

          > Ma question est la suivante : est-il plus rapide de passer ces arguments par référence ou en copiant la variable ? Est-ce plus propre ?

          Ça dépends. Plus de détails dans a suite ;)

          > Passer par référence est forcement plus rapide car les étapes de création de variable et de copie ne sont pas réalisés, ceci dit ce temps est je pense négligeable suivant ce que tu comptes passer par valeur

          Attention tout de même... Une référence une fois la compilation terminée est indiscernable d'un pointeur. En passant par référence, on paie donc 2 couts :

          • La création de la variable de type référence
          • Le fait d'aller chercher la valeur au bon endroit dans la mémoire

          Au final on passe quoi comment du coup? 2 cas de figure :

          • On souhaite modifier la valeur de la variable depuis la fonction : référence
          • On ne souhaite pas modifier la valeur de la variable depuis la fonction : copie pour les types de base (int char double bool etc...) et référence constante pour le reste.
          • Partager sur Facebook
          • Partager sur Twitter
            18 février 2018 à 17:26:18

            Salut,

            De manière générale, on peut partir du principe que l'on va passer les types primitifs (char, short, int, long, long long, float, double, long double) par copie, parce que l'espace mémoire dont ils ont besoin est plus petit ou sensiblement égal à celui qui serait nécessaire pour représenter une référence (qui, au niveau de l'exécutable binaire, sera représenté sous la forme d'un pointeur ; ) ), et que tout ce qui est plus gros qu'un type primitif (typiquement: les classes, les structures et les unions que tu définis) sera transmis par référence, que tu prendras soin de déclarer constante si la fonction appelée ne doit pas modifier l'état de la donnée au niveau de la fonction appelante.

            Après, il y a bien évidemment des "cas particuliers": si tu veux utiliser le retour d'une fonction pour définir une variable, mais que tu as un autre entier qui devrais te permettre de définir une "information connexe" au résultat de ta fonction, tu peux, bien évidemment, décider de transmettre cet entier par référence ;)

            michelbillaud a écrit:

            Le passage du paramètre est une chose, mais il y a ensuite l'utilisation de la variable dans la fonction appelée. Si on a passé une référence, il y aura des indirections.

            Dons si on s'intéresse vraiment au problème d'optimisation, il va falloir y regarder de beaucoup plus près : le contexte, ce qu'on fait avec les variables, des mesures de performances.

            Effectivement, mais une indirection n'est jamais que le résultat d'une instruction d'arithmétique de pointeurs, ce qui sous entend que, en dehors des cas où elle occasionnerait un cache miss, son usage est en temps constant, particulièrement court.

            Si bien que le nombre d'indirections que l'on peut envisager évolue de manière strictement proportionnelle à la complexité de la copie de l'élément transmis en paramètre:

            plus la copie de l'élément prend du temps, plus tu pourras te permettre d'indirections avant que la pratique ne se montre "moins performantes".

            Si bien que l'on pourrait même en arriver à certaines situations "extrêmes" dans lesquelles, même l'apparition d'un cache miss (suivi d'un nombre "relativement faible" d'indirections, s'entend) pourrait encore présenter des performances meilleures que celles occasionnées par la copie ;)

            En général on a de bonnes raisons de passer par référence, si les données qu'on passe sont des objets avec sémantique d'entité, parce que faire une copie n'a même pas de sens.

            Sur ce point, par contre, je suis on ne peut plus d'accord ;)

            • Partager sur Facebook
            • Partager sur Twitter
            Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
              19 février 2018 à 10:33:17

              Pour les paramètres de types primitifs, on peut aussi dire que, si ils sont utilisés par la fonction appelée mais pas transmis à d'autres fonctions, il est  fort probable que leur valeur soit logée dans les registres, et pas dans la pile.

              Bref, pour résumer :

              • la pratique habituelle est de passer les types primitifs par valeur, et les autres par référence
              • mais ce n'est pas une règle qui garantit les meilleurs performances dans tous les cas. La il faut regarder de très près.
              • en général ça ne vaut pas la peine de s'y casser la tête. D'abord se demander si ça vaut le coup de passer des heures pour gagner quelques pourcents. A priori, presque jamais. Exemple : supprimer l'indirection, qui coute peu en elle-même, mais qui coûte quand même, si on la fait un milliard de fois en trop.
              • Mais ça ne veut pas dire qu'il faut fair exprès de programmer comme un sagouin. Donc adopter la pratique habituelle, qui fait assez bien l'affaire.

              -
              Edité par michelbillaud 19 février 2018 à 10:35:33

              • Partager sur Facebook
              • Partager sur Twitter
                21 février 2018 à 1:38:11

                Wow !

                Merci beaucoup pour toutes vos réponses très complètes ! Je vais donc regarder plus en détail chaque variable (j'ai eu la bonne idée de passer tous les arguments par référence, mais ça partait d'une bonne intention) ; cependant, une petite question : qu'est-ce qu'une indirection ?

                Merci encore !

                • Partager sur Facebook
                • Partager sur Twitter
                Merci !
                  21 février 2018 à 4:44:34

                  Pour faire simple : une indirection est le fait de passer par l'adresse mémoire où on (espère) devrait trouver une variable pour pouvoir accéder à cette variable...

                  Alors, l'un dans l'autre, vu que l'ordinateur ne connait que des adresses mémoires, tu as toujours au moins une indirection, dans le sens où, même si tu utilises un entier, le simple fait d'avoir un code proche de

                  i = 5;

                  se traduira, dans les faits, par une instruction proche de "place la valeur 5 à l'adresse mémoire qui est associée à i".

                  Le truc, c'est que ce que l'on appelle "pointeur" n'est en réalité qu'une variable numérique entière (généralement) non signée dont la valeur correspond à l'adresse mémoire à laquelle on est sensé trouver une variable du type indiqué.

                  Du coup, une instruction comme

                  *ptr = 5;

                  se traduira par quelque chose comme "va voir à l'adresse de ptr pour trouver l'adresse à laquelle tu devras placer la valeur 5", ce qui obige -- forcément -- le processeur à accéder à deux adresses mémoire différentes ;)

                  Comme on l'a fait remarquer, ce genre de chose se fait très rapidement (modulo certaines situations très spécifiques), mais ca demande néanmoins un "certain temps" pour être exécuté.  Et tu connais le dicton: ce sont les petits ruisseaux qui font les grands fleuves! 

                  Cela a beau ne pas demander longtemps au processeur de passer chercher une adresse mémoire "quelque part", si tu le fait "suffisamment souvent", tu peux te retrouver à avoir perdu... une heure, simplement à "passer ton temps" à attendre que le processeur n'aille voir "quelque part" à quelle adresse mémoire il doit aller travailler :p

                  • Partager sur Facebook
                  • Partager sur Twitter
                  Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
                    21 février 2018 à 11:10:24

                    Un exemple, on calcule une formule de perlimpimpin avec deux types de passages

                    int produit_par_copie(const int a, const int b, const int c) {
                      return a*b - a*c + b*c;
                    }
                    
                    int produit_par_reference(const int & a, const int & b, const int & c) {
                      return  a*b - a*c + b*c;
                    }
                    
                    

                    et on regarde le code généré par le compilateur. (g++ -O9 -S)

                    Dans le premier cas, les valeurs sont reçues dans les registres (rdi, rsi, esi) et peuvent être traitées directement

                    	movl	%esi, %eax             
                    	subl	%edx, %eax             
                    	imull	%eax, %edi
                    	imull	%edx, %esi
                    	leal	(%rdi,%rsi), %eax
                    	ret
                    


                    dans le second, les registres contiennent les adresses des entiers à traiter, et il faut aller les chercher

                    	movl	(%rsi), %ecx
                    	movl	(%rdx), %esi
                    	movl	%ecx, %eax
                    	subl	%esi, %eax
                    	imull	(%rdi), %eax
                    	imull	%esi, %ecx
                    	leal	(%rax,%rcx), %eax
                    	ret
                    

                    ce qui demande un peu de boulot en plus (et des accès à la mémoire, en plus du travail sur les registres).





                    • Partager sur Facebook
                    • Partager sur Twitter
                      22 février 2018 à 18:42:54

                      Super ! Merci à vous ! Une étude minutieuse de chaque argument est donc requise pour savoir si l'on va gagner ou perdre du temps... Parfait !

                      Merci encore !

                      • Partager sur Facebook
                      • Partager sur Twitter
                      Merci !
                        23 février 2018 à 2:21:56

                        En toute honnêteté, la règle simple "type primitif == valeur, classes, structures, unions == référence" a de très fortes chances de te fournir les meilleures performances dans la très grosse majorité des cas ;)

                        Alors, bien sur, nous sommes bien d'accord sur le fait qu'une indirection, ca n'est pas gratuit, et que cela a un cout...

                        Mais avant de décider de supprimer une indirection quelle qu'elle soit (ici, on parle des références, mais les pointeurs sont aussi des indirections), il faut vraiment:

                        1. s'assurer que l'algorithme est réellement le plus efficace possible et
                        2. faire des benchmarks corrects pour déterminer si, oui ou non, il y a un gain suffisant pour justifier la suppression de l'indirection

                        Notes au passage que si supprimer une indirection signifie provoquer une copie de ta classe ou de ta structure, il y a de fortes chances 

                        1. pour que la copie prenne plus longtemps que l'ensemble des indirections que tu voudras éviter
                        2. pour que tu ne puisse purement et simplement pas copier certaines de tes classes, car elles ont sémantique d'entité (en gros, c'est le cas de toutes les classes intervenant dans une hiérarchie de classes ;)
                        • Partager sur Facebook
                        • Partager sur Twitter
                        Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait

                        Passage pas référence ou copie d'une variable ?

                        × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                        × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                        • Editeur
                        • Markdown