Est-ce que x + = a plus rapide que x = x + a?

84

Je lisais "Le langage de programmation C ++" de Stroustrup, où il dit que deux façons d'ajouter quelque chose à une variable

x = x + a;

et

x += a;

Il préfère +=parce qu'il est probablement mieux mis en œuvre. Je pense qu'il veut dire que ça marche aussi plus vite.
Mais est-ce vraiment? Si cela dépend du compilateur et d'autres choses, comment puis-je vérifier?

Chiffa
la source
45
"Le langage de programmation C ++" a été publié pour la première fois en 1985. La version la plus récente a été publiée en 1997, et une édition spéciale de la version 1997 a été publiée en 2000. En conséquence, certaines parties sont extrêmement obsolètes.
JoeG
5
Les deux lignes pourraient potentiellement faire quelque chose de complètement différent. Vous devez être plus précis.
Kerrek SB
26
Les compilateurs modernes sont suffisamment intelligents pour que ces questions soient considérées comme «dépassées».
gd1 le
2
Rouvert ceci car la question en double pose des questions sur C et non sur C ++.
Kev

Réponses:

212

Tout vaut compilateur son sel va générer exactement la même séquence de langage machine pour les deux constructions pour tout type intégré ( int, float, etc.) tant que la déclaration est vraiment aussi simple que x = x + a; et l' optimisation est activée . (Notamment, GCC -O0, qui est le mode par défaut, effectue des anti-optimisations , telles que l'insertion de magasins complètement inutiles dans la mémoire, afin de s'assurer que les débogueurs peuvent toujours trouver des valeurs de variable.)

Si la déclaration est plus compliquée, cependant, elles peuvent être différentes. Supposons fqu'une fonction renvoie un pointeur, alors

*f() += a;

n'appelle fqu'une seule fois, alors que

*f() = *f() + a;

l'appelle deux fois. S'il fa des effets secondaires, l'un des deux sera faux (probablement le dernier). Même s'il fn'a pas d'effets secondaires, le compilateur peut ne pas être en mesure d'éliminer le deuxième appel, ce dernier peut donc être plus lent.

Et puisque nous parlons de C ++ ici, la situation est entièrement différente pour les types de classes qui surchargent operator+et operator+=. Si xc'est un tel type, alors - avant l'optimisation - se x += atraduit par

x.operator+=(a);

alors que se x = x + atraduit par

auto TEMP(x.operator+(a));
x.operator=(TEMP);

Maintenant, si la classe est correctement écrite et que l'optimiseur du compilateur est assez bon, les deux finiront par générer le même langage machine, mais ce n'est pas une chose sûre comme c'est le cas pour les types intégrés. C'est probablement ce à quoi Stroustrup pense quand il encourage l'utilisation +=.

zwol
la source
14
Il y a aussi un autre aspect - la lisibilité . L'idiome C ++ pour ajouter exprà varis var+=expret l'écrire dans l'autre sens déroutera les lecteurs.
Tadeusz Kopec
21
Si vous vous trouvez en train *f() = *f() + a;d' écrire, vous voudrez peut-être jeter un coup d'œil à ce que vous essayez vraiment de réaliser ...
Adam Davis
3
Et si var = var + expr vous confond, mais pas var + = expr, vous êtes l'ingénieur logiciel le plus étrange que j'ai jamais rencontré. Les deux sont lisibles; assurez-vous simplement que vous êtes cohérent (et nous utilisons tous op =, donc c'est discutable de toute façon = P)
WhozCraig
7
@PiotrDobrogost: Qu'y a-t-il de mal à répondre aux questions? Dans tous les cas, ce devrait être le questionneur qui a vérifié les doublons.
Gorpik
4
@PiotrDobrogost me semble que vous êtes un peu ... jaloux ... Si vous voulez aller chercher des doublons, allez-y. Pour ma part, je préfère répondre aux questions plutôt que de chercher des dupes (à moins que ce ne soit une question que je me souviens spécifiquement avoir vue auparavant). Cela peut parfois être plus rapide et aider celui qui a posé la question plus rapidement. Notez également que ce n'est même pas une boucle. 1est une constante, apeut être un type volatile, défini par l'utilisateur ou autre. Complètement différent. En fait, je ne comprends pas comment cela s'est même fermé.
Luchian Grigore
56

Vous pouvez vérifier en regardant le démontage, qui sera le même.

Pour les types de base , les deux sont également rapides.

Ceci est une sortie générée par une version de débogage (c'est-à-dire sans optimisations):

    a += x;
010813BC  mov         eax,dword ptr [a]  
010813BF  add         eax,dword ptr [x]  
010813C2  mov         dword ptr [a],eax  
    a = a + x;
010813C5  mov         eax,dword ptr [a]  
010813C8  add         eax,dword ptr [x]  
010813CB  mov         dword ptr [a],eax  

Pour les types définis par l'utilisateur , où vous pouvez surcharger operator +et operator +=, cela dépend de leurs implémentations respectives.

Luchian Grigore
la source
1
Pas vrai dans tous les cas. J'ai trouvé qu'il peut être plus rapide de charger une adresse mémoire dans un registre, de l'incrémenter et de la réécrire que d'incrémenter l'emplacement mémoire directement (sans utiliser atomics). Je vais essayer de brouiller le code ...
James
Qu'en est-il des types définis par l'utilisateur? Les bons compilateurs devraient générer un assemblage équivalent, mais il n'y a pas de telle garantie.
mfontanini
1
@LuchianGrigore Nope, si aest 1 et xest volatilele compilateur pourrait générer inc DWORD PTR [x]. C'est lent.
James le
1
@Chiffa ne dépend pas du compilateur, mais dépend du développeur. Vous pouvez implémenter operator +de ne rien faire et operator +=de calculer le 100000e nombre premier, puis de revenir. Bien sûr, ce serait une chose stupide à faire, mais c'est possible.
Luchian Grigore
3
@James: si votre programme est sensible à la différence de performances entre ++xet temp = x + 1; x = temp;, alors il devrait très probablement être écrit en assembly plutôt qu'en c ++ ...
EmirCalabuch
11

Oui! C'est plus rapide à écrire, plus rapide à lire et plus rapide à comprendre, pour ce dernier dans le cas où cela xpourrait avoir des effets secondaires. C'est donc globalement plus rapide pour les humains. Le temps humain en général coûte beaucoup plus cher que le temps informatique, c'est donc ce que vous vouliez savoir. Droite?

Mark Adler
la source
8

Cela dépend vraiment du type de x et a et de l'implémentation de +. Pour

   T x, a;
   ....
   x = x + a;

le compilateur doit créer un T temporaire pour contenir la valeur de x + a pendant qu'il l'évalue, qu'il peut ensuite affecter à x. (Il ne peut pas utiliser x ou a comme espace de travail pendant cette opération).

Pour x + = a, il n'a pas besoin d'un fichier temporaire.

Pour les types triviaux, il n'y a aucune différence.

Tom Tanner
la source
8

La différence entre x = x + aet x += aest la quantité de travail que la machine doit effectuer - certains compilateurs peuvent (et le font généralement) l'optimiser, mais généralement, si nous ignorons l'optimisation pendant un certain temps, ce qui se passe est que dans l'ancien extrait de code, le la machine doit rechercher la valeur xdeux fois, tandis que dans le dernier, cette recherche ne doit se produire qu'une seule fois.

Cependant, comme je l'ai mentionné, aujourd'hui, la plupart des compilateurs sont suffisamment intelligents pour analyser les instructions et réduire les instructions machine résultantes requises.

PS: Première réponse sur Stack Overflow!

Sagar Ahire
la source
6

Comme vous avez étiqueté ce C ++, il n'y a aucun moyen de savoir à partir des deux déclarations que vous avez publiées. Vous devez savoir ce qu'est «x» (c'est un peu comme la réponse «42»). S'il xs'agit d'un POD, cela ne fera pas vraiment de différence. Cependant, si xest une classe, il peut y avoir des surcharges pour les méthodes operator +et operator +=qui pourraient avoir des comportements différents conduisant à des temps d'exécution très différents.

Skizz
la source
6

Si vous dites que +=vous rendez la vie beaucoup plus facile pour le compilateur. Pour que le compilateur reconnaisse que x = x+ac'est la même chose x += a, le compilateur doit

  • analysez le côté gauche ( x) pour vous assurer qu'il n'a pas d'effets secondaires et se réfère toujours à la même valeur l. Par exemple, cela pourrait l'être z[i], et il faut s'assurer que les deux zet ine changent pas.

  • analysez le côté droit ( x+a) et assurez-vous qu'il s'agit d'une sommation, et que le côté gauche apparaît une et une seule fois sur le côté droit, même s'il pourrait être transformé, comme dans z[i] = a + *(z+2*0+i).

Si ce que vous entendez est d'ajouter aà x, l'auteur du compilateur apprécie quand vous dites tout ce que vous voulez dire. De cette façon, vous n'exercez pas la partie du compilateur dont l'auteur espère qu'il / elle a éliminé tous les bugs, et cela ne vous facilite pas vraiment la vie, à moins que vous ne puissiez honnêtement pas sortir la tête. du mode Fortran.

Mike Dunlavey
la source
5

Pour un exemple concret, imaginez un type de nombre complexe simple:

struct complex {
    double x, y;
    complex(double _x, double _y) : x(_x), y(_y) { }
    complex& operator +=(const complex& b) {
        x += b.x;
        y += b.y;
        return *this;
    }
    complex operator +(const complex& b) {
        complex result(x+b.x, y+b.y);
        return result;
    }
    /* trivial assignment operator */
}

Pour le cas a = a + b, il doit créer une variable temporaire supplémentaire, puis la copier.

Aléatoire832
la source
c'est un très bel exemple, montre comment 2 opérateurs implémentés.
Grijesh Chauhan
5

Vous posez la mauvaise question.

Il est peu probable que cela améliore les performances d'une application ou d'une fonctionnalité. Même si c'était le cas, le moyen de le savoir est de profiler le code et de savoir comment il vous affecte à coup sûr. Au lieu de se soucier à ce niveau de ce qui est le plus rapide, il est beaucoup plus important de penser en termes de clarté, d'exactitude et de lisibilité.

Cela est particulièrement vrai si l'on considère que, même s'il s'agit d'un facteur de performance important, les compilateurs évoluent au fil du temps. Quelqu'un peut trouver une nouvelle optimisation et la bonne réponse aujourd'hui peut devenir fausse demain. C'est un cas classique d'optimisation prématurée.

Cela ne veut pas dire que la performance n'a pas d'importance du tout ... C'est simplement la mauvaise approche pour atteindre vos objectifs de performance. La bonne approche consiste à utiliser des outils de profilage pour savoir où votre code passe réellement son temps, et donc où concentrer vos efforts.

Joël Coehoorn
la source
Certes, mais c'était censé être une question de bas niveau, pas une grande image "Quand devrais-je considérer une telle différence".
Chiffa
1
La question du PO était totalement légitime, comme le montrent les réponses des autres (et les votes positifs). Bien que nous comprenions votre point (profil d'abord, etc.), il est certainement intéressant de connaître ce genre de choses - allez-vous vraiment profiler chaque déclaration triviale que vous écrivez, profiler les résultats de chaque décision que vous prenez? Même quand il y a des gens sur SO qui ont déjà étudié, profilé, démonté l'affaire et ont une réponse générale à fournir?
4

Je pense que cela devrait dépendre de la machine et de son architecture. Si son architecture permet l'adressage indirect de la mémoire, le rédacteur du compilateur PEUT simplement utiliser ce code à la place (pour l'optimisation):

mov $[y],$ACC

iadd $ACC, $[i] ; i += y. WHICH MIGHT ALSO STORE IT INTO "i"

Alors que, i = i + ypourrait être traduit en (sans optimisation):

mov $[i],$ACC

mov $[y],$B 

iadd $ACC,$B

mov $B,[i]


Cela dit, d'autres complications telles que if iest une fonction renvoyant un pointeur, etc. doivent également être envisagées. La plupart des compilateurs de niveau production, y compris GCC, produisent le même code pour les deux instructions (si ce sont des entiers).

Aniket Inge
la source
2

Non, les deux manières sont gérées de la même manière.

NuageuxMarbre
la source
10
Pas s'il s'agit d'un type défini par l'utilisateur avec des opérateurs surchargés.