Existe-t-il une différence de performances entre i++
et ++i
si la valeur résultante n'est pas utilisée?
Existe-t-il une différence de performances entre i++
et ++i
si la valeur résultante n'est pas utilisée?
Réponses:
Résumé analytique: Non.
i++
pourrait potentiellement être plus lent que ++i
, car l'ancienne valeur de i
pourrait devoir être sauvegardée pour une utilisation ultérieure, mais dans la pratique, tous les compilateurs modernes optimiseront cela.
Nous pouvons le démontrer en regardant le code de cette fonction, à la fois avec ++i
et i++
.
$ cat i++.c
extern void g(int i);
void f()
{
int i;
for (i = 0; i < 100; i++)
g(i);
}
Les fichiers sont les mêmes, à l'exception de ++i
et i++
:
$ diff i++.c ++i.c
6c6
< for (i = 0; i < 100; i++)
---
> for (i = 0; i < 100; ++i)
Nous les compilerons et obtiendrons également l'assembleur généré:
$ gcc -c i++.c ++i.c
$ gcc -S i++.c ++i.c
Et nous pouvons voir que les fichiers objet et assembleur générés sont identiques.
$ md5 i++.s ++i.s
MD5 (i++.s) = 90f620dda862cd0205cd5db1f2c8c06e
MD5 (++i.s) = 90f620dda862cd0205cd5db1f2c8c06e
$ md5 *.o
MD5 (++i.o) = dd3ef1408d3a9e4287facccec53f7d22
MD5 (i++.o) = dd3ef1408d3a9e4287facccec53f7d22
++i
place de i++
. Il n'y a absolument aucune raison de ne pas le faire, et si votre logiciel passe par une chaîne d'outils qui ne l'optimise pas, votre logiciel sera plus efficace. Étant donné qu'il est tout aussi facile de taper ++i
que de taper i++
, il n'y a vraiment aucune excuse pour ne pas l'utiliser ++i
en premier lieu.
De l' efficacité contre l'intention d'Andrew Koenig:
Premièrement, il est loin d'être évident qu'il
++i
est plus efficace quei++
, du moins en ce qui concerne les variables entières.
Et :
Donc, la question que l'on devrait se poser n'est pas laquelle de ces deux opérations est la plus rapide, mais laquelle de ces deux opérations exprime plus précisément ce que vous essayez d'accomplir. Je soumets que si vous n'utilisez pas la valeur de l'expression, il n'y a jamais de raison d'utiliser à la
i++
place de++i
, car il n'y a jamais de raison de copier la valeur d'une variable, d'incrémenter la variable, puis de jeter la copie.
Donc, si la valeur résultante n'est pas utilisée, j'utiliserais ++i
. Mais pas parce qu'il est plus efficace: parce qu'il énonce correctement mon intention.
i++
la même façon que j'avais le code i += n
ou i = i + n
, par exemple, sous la forme cible verbe objet , avec l' objectif opérande à gauche du verbe opérateur. Dans le cas de i++
, il n'y a pas d' objet droit , mais la règle s'applique toujours, en gardant la cible à gauche de l' opérateur verbe .
Une meilleure réponse est que ce ++i
sera parfois plus rapide mais jamais plus lent.
Tout le monde semble supposer qu'il i
s'agit d'un type intégré standard tel que int
. Dans ce cas, il n'y aura pas de différence mesurable.
Cependant, s'il i
s'agit d'un type complexe, vous pouvez très bien trouver une différence mesurable. Car i++
vous devez faire une copie de votre classe avant de l'incrémenter. Selon ce qui est impliqué dans une copie, cela pourrait en effet être plus lent car avec ++it
vous, vous pouvez simplement retourner la valeur finale.
Foo Foo::operator++()
{
Foo oldFoo = *this; // copy existing value - could be slow
// yadda yadda, do increment
return oldFoo;
}
Une autre différence est qu'avec ++i
vous, vous avez la possibilité de renvoyer une référence au lieu d'une valeur. Encore une fois, selon ce qui est impliqué dans la création d'une copie de votre objet, cela pourrait être plus lent.
Un exemple concret où cela peut se produire serait l'utilisation d'itérateurs. La copie d'un itérateur est peu susceptible d'être un goulot d'étranglement dans votre application, mais il est toujours bon de prendre l'habitude d'utiliser ++i
au lieu de i++
ne pas affecter le résultat.
Réponse courte:
Il n'y a jamais de différence entre i++
et ++i
en termes de vitesse. Un bon compilateur ne devrait pas générer de code différent dans les deux cas.
Longue réponse:
Ce que toutes les autres réponses ne mentionnent pas, c'est que la différence entre ++i
versus i++
n'a de sens que dans l'expression qu'elle trouve.
Dans le cas de for(i=0; i<n; i++)
, le i++
est seul dans sa propre expression: il y a un point de séquence avant le i++
et il y en a un après. Ainsi, le seul code machine généré est "augmenter i
de 1
" et il est bien défini comment il est séquencé par rapport au reste du programme. Donc, si vous le changiez en préfixe ++
, cela n'aurait aucune importance, vous obtiendriez toujours le code machine "augmenter i
de 1
".
Les différences entre ++i
et i++
ne comptent que dans des expressions telles que array[i++] = x;
versus array[++i] = x;
. Certains peuvent argumenter et dire que le suffixe sera plus lent dans de telles opérations car le registre où i
réside doit être rechargé plus tard. Mais notez ensuite que le compilateur est libre de commander vos instructions comme bon lui semble, tant qu'il ne "casse pas le comportement de la machine abstraite" comme l'appelle la norme C.
Donc, alors que vous pouvez supposer que cela array[i++] = x;
est traduit en code machine comme:
i
dans le registre A.i
registre A // inefficace car des instructions supplémentaires ici, nous l'avons déjà fait une fois.i
.le compilateur pourrait aussi bien produire le code plus efficacement, comme:
i
dans le registre A.i
.Tout simplement parce que vous, en tant que programmeur C, êtes entraîné à penser que le suffixe ++
se produit à la fin, le code machine ne doit pas être commandé de cette façon.
Il n'y a donc pas de différence entre le préfixe et le suffixe ++
en C. Maintenant, en tant que programmeur C, vous devriez être différent, ce sont les personnes qui utilisent le préfixe de manière incohérente dans certains cas et le suffixe dans d'autres cas, sans aucune justification. Cela suggère qu'ils ne savent pas comment C fonctionne ou qu'ils ont une mauvaise connaissance de la langue. C'est toujours un mauvais signe, cela suggère à leur tour qu'ils prennent d'autres décisions douteuses dans leur programme, basées sur la superstition ou des "dogmes religieux".
"Le préfixe ++
est toujours plus rapide" est en effet un tel faux dogme qui est courant parmi les programmeurs C potentiels.
Prenant une feuille de Scott Meyers, Plus efficace c ++ Point 6: Distinguer les formes préfixées et postfixes des opérations d'incrémentation et de décrémentation .
La version préfixe est toujours préférée au suffixe en ce qui concerne les objets, en particulier en ce qui concerne les itérateurs.
La raison de cela si vous regardez le modèle d'appel des opérateurs.
// Prefix
Integer& Integer::operator++()
{
*this += 1;
return *this;
}
// Postfix
const Integer Integer::operator++(int)
{
Integer oldValue = *this;
++(*this);
return oldValue;
}
En regardant cet exemple, il est facile de voir comment l'opérateur de préfixe sera toujours plus efficace que le suffixe. En raison de la nécessité d'un objet temporaire dans l'utilisation du suffixe.
C'est pourquoi lorsque vous voyez des exemples utilisant des itérateurs, ils utilisent toujours la version du préfixe.
Mais comme vous le signalez pour les int, il n'y a effectivement aucune différence en raison de l'optimisation du compilateur qui peut avoir lieu.
Voici une observation supplémentaire si vous vous inquiétez de la micro-optimisation. Les boucles de décrémentation peuvent «éventuellement» être plus efficaces que les boucles d'incrémentation (selon l'architecture du jeu d'instructions, par exemple ARM), étant donné:
for (i = 0; i < 100; i++)
Sur chaque boucle, vous aurez chacun une instruction pour:
1
à i
. i
est inférieur à a 100
.i
est inférieure à a 100
.Alors qu'une boucle décrémentante:
for (i = 100; i != 0; i--)
La boucle aura une instruction pour chacun des éléments suivants:
i
, en définissant l'indicateur d'état du registre CPU.Z==0
).Bien sûr, cela ne fonctionne que lors d'une décrémentation à zéro!
Rappelé dans le Guide du développeur d'ARM System.
Veuillez ne pas laisser la question de "lequel est le plus rapide" être le facteur décisif à utiliser. Il est probable que vous ne vous en soucierez jamais autant, et en outre, le temps de lecture du programmeur est beaucoup plus cher que le temps machine.
Utilisez celui qui a le plus de sens pour l'homme qui lit le code.
Tout d'abord: la différence entre i++
et ++i
est négligeable en C.
Pour les détails.
++i
est plus rapideEn C ++, ++i
est plus efficace si siff i
est une sorte d'objet avec un opérateur d'incrémentation surchargé.
Pourquoi?
Dans ++i
, l'objet est d'abord incrémenté et peut ensuite être transmis comme référence const à toute autre fonction. Ce n'est pas possible si l'expression est foo(i++)
parce que maintenant l'incrément doit être fait avant d' foo()
être appelé, mais l'ancienne valeur doit être passée à foo()
. Par conséquent, le compilateur est obligé de faire une copie de i
avant d'exécuter l'opérateur d'incrémentation sur l'original. Les appels supplémentaires de constructeur / destructeur sont la mauvaise partie.
Comme indiqué ci-dessus, cela ne s'applique pas aux types fondamentaux.
i++
peut être plus rapideSi aucun constructeur / destructeur n'a besoin d'être appelé, ce qui est toujours le cas en C, ++i
et i++
devrait être tout aussi rapide, non? Non. Ils sont pratiquement aussi rapides, mais il peut y avoir de petites différences, que la plupart des autres répondants ont mal résolues.
Comment peut-on i++
être plus rapide?
Il s'agit des dépendances aux données. Si la valeur doit être chargée à partir de la mémoire, deux opérations subséquentes doivent être effectuées avec elle, l'incrémenter et l'utiliser. Avec ++i
, l'incrémentation doit être effectuée avant que la valeur puisse être utilisée. Avec i++
, l'utilisation ne dépend pas de l'incrément et la CPU peut effectuer l'opération d'utilisation en parallèle de l'opération d'incrément. La différence est au plus un cycle CPU, donc c'est vraiment négociables, mais c'est là. Et c'est l'inverse alors que beaucoup s'attendraient.
++i
ou i++
est utilisé dans une autre expression, le changement entre les deux change la sémantique de l'expression, donc tout gain / perte de performance possible est hors de question. S'ils sont autonomes, c'est-à-dire que le résultat de l'opération n'est pas utilisé immédiatement, alors n'importe quel compilateur décent le compilera pour la même chose, par exemple une INC
instruction d'assemblage.
i++
et ++i
peuvent être utilisés de façon interchangeable dans presque toutes les situations possibles en ajustant les constantes de boucle par un, ils sont donc équivalent dans ce qu'ils font pour le programmeur. 2) Même si les deux compilent selon la même instruction, leur exécution diffère pour le CPU. Dans le cas de i++
, le CPU peut calculer l'incrément en parallèle à une autre instruction qui utilise la même valeur (les CPU le font vraiment!), Tandis qu'avec ++i
le CPU il doit planifier l'autre instruction après l'incrément.
if(++foo == 7) bar();
et if(foo++ == 6) bar();
sont fonctionnellement équivalents. Cependant, le second peut être un cycle plus rapide, car la comparaison et l'incrément peuvent être calculés en parallèle par le CPU. Ce n'est pas que ce cycle unique compte beaucoup, mais la différence est là.
<
par exemple vs <=
) où ++
est généralement utilisé, donc la conversion entre le bien est souvent facilement possible.
@Mark Même si le compilateur est autorisé à optimiser la copie temporaire (basée sur la pile) de la variable et que gcc (dans les versions récentes) le fait, cela ne signifie pas que tous les compilateurs le feront toujours.
Je viens de le tester avec les compilateurs que nous utilisons dans notre projet actuel et 3 sur 4 ne l'optimisent pas.
Ne présumez jamais que le compilateur réussit, surtout si le code éventuellement plus rapide mais jamais plus lent est aussi facile à lire.
Si vous n'avez pas d'implémentation vraiment stupide de l'un des opérateurs de votre code:
J'ai toujours préféré ++ i à i ++.
En C, le compilateur peut généralement les optimiser pour qu'elles soient identiques si le résultat n'est pas utilisé.
Cependant, en C ++ si vous utilisez d'autres types qui fournissent leurs propres opérateurs ++, la version préfixe est susceptible d'être plus rapide que la version postfix. Donc, si vous n'avez pas besoin de la sémantique postfix, il est préférable d'utiliser l'opérateur de préfixe.
Je peux penser à une situation où postfix est plus lent que l'incrément de préfixe:
Imaginez un processeur avec registre A
utilisé comme accumulateur et c'est le seul registre utilisé dans de nombreuses instructions (certains petits microcontrôleurs sont en fait comme ça).
Imaginez maintenant le programme suivant et sa traduction en un assemblage hypothétique:
Incrément de préfixe:
a = ++b + c;
; increment b
LD A, [&b]
INC A
ST A, [&b]
; add with c
ADD A, [&c]
; store in a
ST A, [&a]
Incrément de suffixe:
a = b++ + c;
; load b
LD A, [&b]
; add with c
ADD A, [&c]
; store in a
ST A, [&a]
; increment b
LD A, [&b]
INC A
ST A, [&b]
Notez comment la valeur de a b
été forcée d'être rechargée. Avec l'incrémentation du préfixe, le compilateur peut simplement incrémenter la valeur et continuer à l'utiliser, évitant éventuellement de la recharger car la valeur souhaitée est déjà dans le registre après l'incrément. Cependant, avec l'incrémentation de suffixe, le compilateur doit gérer deux valeurs, l'une ancienne et l'autre la valeur incrémentée qui, comme je le montre ci-dessus, entraîne un accès mémoire supplémentaire.
Bien sûr, si la valeur de l'incrément n'est pas utilisée, comme une seule i++;
instruction, le compilateur peut (et fait) simplement générer une instruction d'incrément indépendamment de l'utilisation du suffixe ou du préfixe.
En remarque, je voudrais mentionner qu'une expression dans laquelle il y a un b++
ne peut pas être simplement convertie en une avec ++b
sans aucun effort supplémentaire (par exemple en ajoutant un - 1
). Donc, comparer les deux s'ils font partie d'une expression n'est pas vraiment valable. Souvent, lorsque vous utilisez b++
une expression que vous ne pouvez pas utiliser ++b
, même si elle ++b
était potentiellement plus efficace, ce serait tout simplement faux. L'exception est bien sûr si l'expression le demande (par exemple a = b++ + 1;
qui peut être changé en a = ++b;
).
J'ai lu la plupart des réponses ici et de nombreux commentaires, et je n'ai vu aucune référence au seul cas où je pourrais penser où i++
est plus efficace que ++i
(et peut-être étonnamment --i
était plus efficace que i--
). C'est pour les compilateurs C pour le DEC PDP-11!
Le PDP-11 avait des instructions d'assemblage pour la pré-décrémentation d'un registre et la post-incrémentation, mais pas l'inverse. Les instructions permettaient à tout registre "à usage général" d'être utilisé comme pointeur de pile. Donc, si vous avez utilisé quelque chose comme *(i++)
ça, cela pourrait être compilé en une seule instruction d'assemblage, alors que *(++i)
non.
Ceci est évidemment un exemple très ésotérique, mais il fournit l'exception lorsque post-augmentation est plus efficace (ou devrais - je dire était , car il n'y a pas beaucoup de demande pour le PDP-11 code C ces jours -ci ).
--i
et i++
.
Je préfère toujours le pré-incrément, cependant ...
Je voulais souligner que même dans le cas de l'appel de la fonction operator ++, le compilateur sera en mesure d'optimiser le temporaire si la fonction est en ligne. Étant donné que l'opérateur ++ est généralement court et souvent implémenté dans l'en-tête, il est probable qu'il soit intégré.
Donc, à des fins pratiques, il n'y a probablement pas beaucoup de différence entre les performances des deux formulaires. Cependant, je préfère toujours le pré-incrément, car il semble préférable d'exprimer directement ce que j'essaie de dire, plutôt que de compter sur l'optimiseur pour le comprendre.
En outre, donner moins à l'optmizer de faire signifie probablement que le compilateur s'exécute plus rapidement.
Mon C est un peu rouillé, je m'excuse donc à l'avance. Speedwise, je peux comprendre les résultats. Mais, je suis confus quant à la façon dont les deux fichiers sont sortis sur le même hachage MD5. Peut-être qu'une boucle for fonctionne de la même manière, mais les 2 lignes de code suivantes ne généreraient-elles pas un assemblage différent?
myArray[i++] = "hello";
contre
myArray[++i] = "hello";
Le premier écrit la valeur dans le tableau, puis incrémente i. Le deuxième incrément i écrit ensuite dans le tableau. Je ne suis pas un expert en assemblage, mais je ne vois pas comment le même exécutable serait généré par ces 2 lignes de code différentes.
Juste mes deux cents.
foo[i++]
en foo[++i]
sans changer autre chose changerait évidemment la sémantique du programme, mais sur certains processeurs lors de l'utilisation d'un compilateur sans logique d'optimisation de levage de boucle, l'incrémentation p
et q
une fois puis l'exécution d'une boucle qui fonctionne, par exemple, *(p++)=*(q++);
serait plus rapide que d'utiliser une boucle qui fonctionne *(++pp)=*(++q);
. Pour les boucles très serrées sur certains processeurs, la différence de vitesse peut être significative (plus de 10%), mais c'est probablement le seul cas en C où le post-incrément est sensiblement plus rapide que le pré-incrément.