Nous avons la question est-il une différence de performance entre i++
et ++i
en C ?
Quelle est la réponse pour C ++?
c++
performance
oop
post-increment
pre-increment
Mark Harrison
la source
la source
Réponses:
[Résumé: à utiliser
++i
si vous n'avez pas de raison spécifique d'utiliseri++
.]Pour C ++, la réponse est un peu plus compliquée.
Si
i
est un type simple (pas une instance d'une classe C ++), alors la réponse donnée pour C ("Non, il n'y a pas de différence de performances") est valable, car le compilateur génère le code.Cependant, si
i
est une instance d'une classe C ++, alorsi++
et++i
effectuent des appels à l'une desoperator++
fonctions. Voici une paire standard de ces fonctions:Puisque le compilateur ne génère pas de code, mais appelle simplement une
operator++
fonction, il n'y a aucun moyen d'optimiser latmp
variable et son constructeur de copie associé. Si le constructeur de copie coûte cher, cela peut avoir un impact significatif sur les performances.la source
Oui. Il y a.
L'opérateur ++ peut être défini ou non comme une fonction. Pour les types primitifs (int, double, ...) les opérateurs sont intégrés, donc le compilateur sera probablement en mesure d'optimiser votre code. Mais dans le cas d'un objet qui définit l'opérateur ++, les choses sont différentes.
La fonction operator ++ (int) doit créer une copie. En effet, postfix ++ devrait renvoyer une valeur différente de celle qu'il contient: il doit conserver sa valeur dans une variable temporaire, incrémenter sa valeur et renvoyer la température. Dans le cas de l'opérateur ++ (), préfixe ++, il n'est pas nécessaire de créer une copie: l'objet peut s'incrémenter puis se retourner simplement.
Voici une illustration du point:
Chaque fois que vous appelez operator ++ (int), vous devez créer une copie et le compilateur ne peut rien y faire. Lorsque vous avez le choix, utilisez operator ++ (); de cette façon, vous n'enregistrez pas de copie. Il peut être important dans le cas de nombreux incréments (grande boucle?) Et / ou de gros objets.
la source
C t(*this); ++(*this); return t;
Sur la deuxième ligne, vous incrémentez le pointeur this à droite, alors comment est-t
il mis à jour si vous l'incrémentez. Les valeurs de cela n'étaient-elles pas déjà copiéest
?The operator++(int) function must create a copy.
non, ça ne l'est pas. Pas plus de copies queoperator++()
Voici une référence pour le cas où les opérateurs d'incrémentation sont dans différentes unités de traduction. Compilateur avec g ++ 4.5.
Ignorez les problèmes de style pour l'instant
Incrément O (n)
Tester
Résultats
Résultats (les temps sont en secondes) avec g ++ 4.5 sur une machine virtuelle:
Incrément O (1)
Tester
Prenons maintenant le fichier suivant:
Cela ne fait rien dans l'incrémentation. Cela simule le cas où l'incrémentation a une complexité constante.
Résultats
Les résultats varient désormais considérablement:
Conclusion
Côté performance
Si vous n'avez pas besoin de la valeur précédente, prenez l'habitude d'utiliser le pré-incrément. Soyez cohérent même avec les types intégrés, vous vous y habituerez et ne courrez pas le risque de subir des pertes de performances inutiles si vous remplacez un type intégré par un type personnalisé.
Sémantique
i++
ditincrement i, I am interested in the previous value, though
.++i
ditincrement i, I am interested in the current value
ouincrement i, no interest in the previous value
. Encore une fois, vous vous y habituerez, même si vous n'êtes pas en ce moment.Knuth.
L'optimisation prématurée est la racine de tout Mal. Tout comme la pessimisation prématurée.
la source
for (it=nearest(ray.origin); it!=end(); ++it) { if (auto i = intersect(ray, *it)) return i; }
, don't mind the actual tree structure (BSP, kd, Quadtree, Octree Grid, etc.). Une telle iterator aurait besoin de maintenir un certain état, par exempleparent node
,child node
,index
et d'autres choses comme ça. Dans l'ensemble, ma position est, même si seuls quelques exemples existent, ...Il n'est pas tout à fait correct de dire que le compilateur ne peut pas optimiser la copie de variable temporaire dans le cas de postfix. Un test rapide avec VC montre qu'il peut au moins le faire dans certains cas.
Dans l'exemple suivant, le code généré est identique pour le préfixe et le suffixe, par exemple:
Que vous fassiez ++ testFoo ou testFoo ++, vous obtiendrez toujours le même code résultant. En fait, sans lire le compte de l'utilisateur, l'optimiseur a réduit le tout à une constante. Donc ça:
Résultat:
Donc, bien qu'il soit certain que la version postfixe pourrait être plus lente, il se pourrait bien que l'optimiseur soit suffisamment bon pour se débarrasser de la copie temporaire si vous ne l'utilisez pas.
la source
Le guide de style Google C ++ dit:
la source
Je voudrais souligner un excellent article d'Andrew Koenig sur Code Talk très récemment.
http://dobbscodetalk.com/index.php?option=com_myblog&show=Efficiency-versus-intent.html&Itemid=29
Dans notre entreprise, nous utilisons également la convention de ++ iter pour la cohérence et les performances, le cas échéant. Mais Andrew soulève des détails négligés concernant l'intention vs la performance. Il y a des moments où nous voulons utiliser iter ++ au lieu de ++ iter.
Donc, décidez d'abord de votre intention et si pré ou post n'a pas d'importance, alors allez avec pre car cela aura un avantage en termes de performances en évitant de créer un objet supplémentaire et en le jetant.
la source
@Ketan
De toute évidence, le post et le pré-incrément ont une sémantique différente et je suis sûr que tout le monde convient que lorsque le résultat est utilisé, vous devez utiliser l'opérateur approprié. Je pense que la question est de savoir ce qu'il faut faire lorsque le résultat est rejeté (comme dans les
for
boucles). La réponse à cette question (à mon humble avis) est que, puisque les considérations de performances sont au mieux négligeables, vous devez faire ce qui est plus naturel. Pour moi,++i
c'est plus naturel, mais mon expérience me dit que je suis en minorité et que l'utilisationi++
entraînera moins de surcharge métallique pour la plupart des gens qui lisent votre code.Après tout, c'est la raison pour laquelle la langue n'est pas appelée "
++C
". [*][*] Insérer une discussion obligatoire sur le fait d'
++C
être un nom plus logique.la source
Lorsqu'il n'utilise pas la valeur de retour, le compilateur est garanti de ne pas utiliser de temporaire dans le cas de ++ i . Pas garanti pour être plus rapide, mais pas pour être plus lent.
Lorsque vous utilisez la valeur de retour, i ++ permet au processeur de pousser à la fois l'incrément et le côté gauche dans le pipeline car ils ne dépendent pas les uns des autres. ++ je peux bloquer le pipeline parce que le processeur ne peut pas démarrer le côté gauche jusqu'à ce que l'opération de pré-incrémentation ait serpenté tout au long. Encore une fois, un blocage de pipeline n'est pas garanti, car le processeur peut trouver d'autres choses utiles à coller.
la source
Mark: Je voulais juste souligner que les ++ de l'opérateur sont de bons candidats pour être intégrés, et si le compilateur choisit de le faire, la copie redondante sera éliminée dans la plupart des cas. (Par exemple, les types de POD, qui sont généralement les itérateurs.)
Cela dit, il est toujours préférable d'utiliser ++ iter dans la plupart des cas. :-)
la source
La différence de performances entre
++i
eti++
sera plus apparente lorsque vous considérerez les opérateurs comme des fonctions de retour de valeur et la façon dont ils sont implémentés. Pour faciliter la compréhension de ce qui se passe, les exemples de code suivants utiliserontint
comme s'il s'agissait d'unstruct
.++i
incrémente la variable, puis renvoie le résultat. Cela peut être fait sur place et avec un temps processeur minimal, ne nécessitant qu'une seule ligne de code dans de nombreux cas:Mais on ne peut pas en dire autant
i++
.Après l'incrémentation,,
i++
est souvent considéré comme renvoyant la valeur d'origine avant l' incrémentation. Cependant, une fonction ne peut renvoyer un résultat que lorsqu'elle est terminée . Par conséquent, il devient nécessaire de créer une copie de la variable contenant la valeur d'origine, d'incrémenter la variable, puis de renvoyer la copie contenant la valeur d'origine:Lorsqu'il n'y a pas de différence fonctionnelle entre pré-incrémentation et post-incrémentation, le compilateur peut effectuer une optimisation telle qu'il n'y ait pas de différence de performances entre les deux. Cependant, si un type de données composite tel que a
struct
ouclass
est impliqué, le constructeur de copie sera appelé en post-incrémentation et il ne sera pas possible d'effectuer cette optimisation si une copie complète est nécessaire. En tant que tel, le pré-incrément est généralement plus rapide et nécessite moins de mémoire que le post-incrément.la source
@Mark: J'ai supprimé ma réponse précédente parce que c'était un peu flip, et méritais un downvote pour cela seul. En fait, je pense que c'est une bonne question dans le sens où elle demande ce que beaucoup de gens pensent.
La réponse habituelle est que ++ i est plus rapide que i ++, et c'est certainement le cas, mais la plus grande question est "quand devriez-vous vous en soucier?"
Si la fraction du temps processeur consacré à l'incrémentation des itérateurs est inférieure à 10%, cela ne vous dérange pas.
Si la fraction du temps processeur consacré à l'incrémentation des itérateurs est supérieure à 10%, vous pouvez voir quelles instructions effectuent cette itération. Voyez si vous pouvez simplement incrémenter des entiers plutôt que d'utiliser des itérateurs. Il y a de fortes chances que vous le puissiez, et bien que cela puisse être dans un certain sens moins souhaitable, les chances sont plutôt bonnes que vous économiserez essentiellement tout le temps passé dans ces itérateurs.
J'ai vu un exemple où l'incrémentation d'itérateur consommait bien plus de 90% du temps. Dans ce cas, passer à l'incrémentation d'entier a réduit le temps d'exécution essentiellement de ce montant. (c'est-à-dire meilleur que 10x d'accélération)
la source
@wilhelmtell
Le compilateur peut éluder le temporaire. Verbatim de l'autre thread:
Le compilateur C ++ est autorisé à éliminer les temporaires basés sur la pile même si cela modifie le comportement du programme. Lien MSDN pour VC 8:
http://msdn.microsoft.com/en-us/library/ms364057(VS.80).aspx
la source
Une raison pour laquelle vous devez utiliser ++ i même sur des types intégrés où il n'y a aucun avantage en termes de performances est de créer une bonne habitude pour vous-même.
la source
Les deux sont aussi rapides;) Si vous voulez que ce soit le même calcul pour le processeur, c'est juste l'ordre dans lequel cela se fait qui diffère.
Par exemple, le code suivant:
Produisez l'assemblage suivant:
Vous voyez que pour a ++ et b ++ c'est un mnémonique incl, donc c'est la même opération;)
la source
La question voulue était de savoir quand le résultat n'est pas utilisé (cela ressort clairement de la question pour C). Quelqu'un peut-il résoudre ce problème puisque la question est "wiki communautaire"?
Concernant les optimisations prématurées, Knuth est souvent cité. C'est vrai. mais Donald Knuth ne défendrait jamais avec cela le code horrible que vous pouvez voir de nos jours. Avez-vous déjà vu a = b + c parmi les entiers Java (pas int)? Cela équivaut à 3 conversions de boxe / unboxing. Il est important d'éviter des trucs comme ça. Et écrire inutilement i ++ au lieu de ++ i est la même erreur. EDIT: Comme phresnel le dit gentiment dans un commentaire, cela peut être résumé comme "l'optimisation prématurée est mauvaise, tout comme la pessimisation prématurée".
Même le fait que les gens soient plus habitués à i ++ est un héritage malheureux en C, causé par une erreur conceptuelle de K&R (si vous suivez l'argument de l'intention, c'est une conclusion logique; et défendre K&R parce qu'ils sont K&R n'a pas de sens, ils sont génial, mais ils ne sont pas excellents en tant que concepteurs de langage; d'innombrables erreurs dans la conception C existent, allant de gets () à strcpy (), à l'API strncpy () (il aurait dû avoir l'API strlcpy () depuis le premier jour) ).
Btw, je fais partie de ceux qui ne sont pas assez habitués au C ++ pour trouver ++ i ennuyeux à lire. Pourtant, je l'utilise car je reconnais que c'est juste.
la source
++i
plus ennuyeux quei++
(en fait, je l'ai trouvé plus frais), mais le reste de votre message obtient ma pleine reconnaissance. Peut-être ajouter un point "l'optimisation prématurée est mauvaise, tout comme la pessimisation prématurée"strncpy
servi un objectif dans les systèmes de fichiers qu'ils utilisaient à l'époque; le nom de fichier était un tampon de 8 caractères et il ne devait pas être terminé par null. Vous ne pouvez pas leur reprocher de ne pas voir 40 ans dans l'avenir de l'évolution du langage.strlcpy()
était justifiée par le fait qu'elle n'avait pas encore été inventée.Il est temps de fournir aux gens des joyaux de sagesse;) - il y a une astuce simple pour que l'incrémentation postfixe C ++ se comporte à peu près comme l'incrémentation du préfixe (j'ai inventé cela pour moi, mais je l'ai vu aussi dans le code d'autres personnes, donc je ne seul).
Fondamentalement, l'astuce consiste à utiliser la classe d'assistance pour reporter l'incrémentation après le retour, et RAII vient à la rescousse
Invented est destiné à certains codes d'itérateurs personnalisés lourds et réduit le temps d'exécution. Le coût du préfixe par rapport au postfix est une référence maintenant, et s'il s'agit d'un opérateur personnalisé effectuant de lourds déplacements, le préfixe et le postfix ont produit le même temps d'exécution pour moi.
la source
++i
est plus rapide quei++
parce qu'il ne retourne pas une ancienne copie de la valeur.C'est aussi plus intuitif:
Cet exemple C imprime "02" au lieu du "12" auquel vous pourriez vous attendre:
Idem pour C ++ :
la source