Question

Y a-t-il une différence de performances entre i++ et ++i si la valeur résultante n'est pas utilisée ?

Était-ce utile?

La solution

Résumé exécutif:Non.

i++ pourrait potentiellement être plus lent que ++i, puisque l'ancienne valeur de ipourrait avoir besoin d'être enregistré pour une utilisation ultérieure, mais dans la pratique, tous les compilateurs modernes l'optimiseront.

Nous pouvons le démontrer en regardant le code de cette fonction, les deux 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, sauf pour ++i et i++:

$ diff i++.c ++i.c
6c6
<     for (i = 0; i < 100; i++)
---
>     for (i = 0; i < 100; ++i)

Nous allons les compiler, et également obtenir 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 les mêmes.

$ md5 i++.s ++i.s
MD5 (i++.s) = 90f620dda862cd0205cd5db1f2c8c06e
MD5 (++i.s) = 90f620dda862cd0205cd5db1f2c8c06e

$ md5 *.o
MD5 (++i.o) = dd3ef1408d3a9e4287facccec53f7d22
MD5 (i++.o) = dd3ef1408d3a9e4287facccec53f7d22

Autres conseils

Depuis Efficacité versus intention de Andrew Koenig :

Premièrement, il est loin d’être évident que ++i est plus efficace que i++, du moins en ce qui concerne les variables entières.

Et :

La question qu’il faut se poser n’est donc pas laquelle de ces deux opérations est la plus rapide, mais laquelle de ces deux opérations exprime le 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 i++ au lieu 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 que c’est plus efficace :parce qu'il énonce correctement mon intention.

Une meilleure réponse est que ++i sera parfois plus rapide mais jamais plus lent.

Tout le monde semble supposer que i est un type intégré régulier tel que int.Dans ce cas, il n’y aura aucune différence mesurable.

Toutefois, si i est un type complexe, alors vous pourriez bien trouver une différence mesurable.Pour i++ vous devez faire une copie de votre classe avant de l'incrémenter.En fonction de ce qu'implique une copie, celle-ci peut effectivement être plus lente, car avec ++it vous pouvez simplement renvoyer 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 avez la possibilité de renvoyer une référence au lieu d'une valeur.Encore une fois, en fonction de ce qu'implique la création d'une copie de votre objet, cela peut être plus lent.

Un exemple concret de cas où cela peut se produire serait l’utilisation d’itérateurs.Il est peu probable que copier un itérateur soit un goulot d'étranglement dans votre application, mais il est toujours bon de prendre l'habitude d'utiliser ++i au lieu de i++ où le résultat n’est pas affecté.

S'inspirant de Scott Meyers, C++ plus efficace Article 6 :Distinguer les formes préfixe et postfixe des opérations d'incrémentation et de décrémentation.

La version du préfixe est toujours préférée au suffixe en ce qui concerne les objets, notamment en ce qui concerne les itérateurs.

La raison en est si l’on regarde 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 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 faites remarquer 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 (en fonction de l'architecture du jeu d'instructions, par ex.ARM), étant donné :

for (i = 0; i < 100; i++)

Sur chaque boucle vous aurez chacun une instruction pour :

  1. Ajouter 1 à i.
  2. Comparez si i est inférieur à un 100.
  3. Un branchement conditionnel si i est inférieur à un 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 :

  1. Décrémenter i, définissant l'indicateur d'état du registre du processeur.
  2. Une branche conditionnelle en fonction de l'état du registre CPU (Z==0).

Bien sûr, cela ne fonctionne qu'en décrémentant jusqu'à zéro !

Rappelé du Guide du développeur du système ARM.

Réponse courte:

Il n'y a jamais de différence entre i++ et ++i en termes de vitesse.Un bon compilateur ne doit pas générer de code différent dans les deux cas.

Longue réponse:

Ce que toutes les autres réponses oublient de mentionner, c'est que la différence entre ++i contre i++ n'a de sens que dans l'expression dans laquelle on le trouve.

Dans le cas d 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 par 1" et la manière dont cela s'enchaîne par rapport au reste du programme est bien définie.Donc si vous vouliez le changer en préfixe ++, cela n'aurait aucune importance, vous obtiendriez toujours simplement le code machine "augmenter i par 1".

Les différences entre ++i et i++ n'a d'importance que dans des expressions telles que array[i++] = x; contre array[++i] = x;.Certains diront peut-être que le suffixe sera plus lent dans de telles opérations car le registre où i les résidents doivent être rechargés plus tard.Mais notez ensuite que le compilateur est libre d'ordonner vos instructions comme bon lui semble, tant qu'il ne « brise pas le comportement de la machine abstraite » comme l'appelle le standard C.

Ainsi, même si vous pouvez supposer que array[i++] = x; est traduit en code machine comme suit :

  • Stocker la valeur de i dans le registre A.
  • Stocker l'adresse du tableau dans le registre B.
  • Ajoutez A et B, stockez les résultats dans A.
  • A cette nouvelle adresse représentée par A, stockez la valeur de x.
  • Stocker la valeur de i dans le registre A // inefficace car instruction supplémentaire ici, nous l'avons déjà fait une fois.
  • Incrémenter le registre A.
  • Enregistrer le registre A dans i.

le compilateur pourrait tout aussi bien produire le code plus efficacement, comme :

  • Stocker la valeur de i dans le registre A.
  • Stocker l'adresse du tableau dans le registre B.
  • Ajoutez A et B, stockez les résultats dans B.
  • Incrémenter le registre A.
  • Enregistrer le registre A dans i.
  • ...// reste du code.

Tout simplement parce que vous, en tant que programmeur C, êtes formé à penser que le suffixe ++ se produit à la fin, le code machine n'a pas besoin d'être ordonné de cette façon.

Il n'y a donc aucune différence entre préfixe et suffixe ++ en C.Maintenant, en tant que programmeur C, vous devriez être différent, ce sont les personnes qui utilisent de manière incohérente le préfixe dans certains cas et le suffixe dans d'autres cas, sans aucune raison.Cela suggère qu’ils ne savent pas comment fonctionne C ou qu’ils ont une connaissance incorrecte du langage.C'est toujours un mauvais signe, cela suggère en retour qu'ils prennent d'autres décisions douteuses dans leur programme, basées sur la superstition ou les « dogmes religieux ».

"Préfixe ++ est toujours plus rapide" est en effet l'un de ces faux dogmes qui est courant parmi les futurs programmeurs C.

S'il vous plaît, ne laissez pas la question de savoir « lequel est le plus rapide » être le facteur décisif quant à l'utilisation.Il y a de fortes chances que vous ne vous en souciiez jamais beaucoup, et en plus, le temps de lecture du programmeur est bien plus cher que le temps de la machine.

Utilisez celui qui a le plus de sens pour l’humain qui lit le code.

Tout d'abord:La différence entre i++ et ++i est négligeable en C.


Aux détails.

1.Le problème bien connu du C++ : ++i est plus rapide

En C++, ++i est plus efficace ssi 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.Ceci n'est pas possible si l'expression est foo(i++) parce que maintenant l'incrément doit être fait avant foo() est appelé, mais l'ancienne valeur doit être transmise à 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 constructeur/destructeur supplémentaires sont le mauvais côté.

Comme indiqué ci-dessus, cela ne s'applique pas aux types fondamentaux.

2.Le fait peu connu : i++ peut Être plus rapide

Si aucun constructeur/destructeur ne doit ê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 se sont trompées.

Comment puis i++ Être plus rapide?
Le problème, ce sont les dépendances des données.Si la valeur doit être chargée depuis la mémoire, deux opérations ultérieures doivent être effectuées avec elle, en l'incrémentant et en l'utilisant.Avec ++i, l'incrémentation doit être effectuée avant la valeur peut être utilisée.Avec i++, l'utilisation ne dépend pas de l'incrément, et le CPU peut effectuer l'opération d'utilisation en parallèle à l’opération d’incrémentation.La différence est d'au plus un cycle CPU, elle est donc vraiment négligeable, mais elle est là.Et c’est l’inverse que beaucoup attendraient.

@Mark Même si le compilateur est autorisé à optimiser la copie temporaire (basée sur la pile) de la variable et GCC (dans les versions récentes), cela ne signifie pas 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 fait les choses correctement, surtout si le code, peut-être plus rapide, mais jamais plus lent, est aussi facile à lire.

Si vous n'avez pas une implémentation vraiment stupide de l'un des opérateurs dans votre code :

Je préfère toujours ++i à i++.

En C, le compilateur peut généralement les optimiser pour qu'ils soient identiques si le résultat n'est pas utilisé.

Cependant, en C++, si vous utilisez d'autres types fournissant leurs propres opérateurs ++, la version préfixe sera probablement 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 préfixe.

Je peux penser à une situation où le postfix est plus lent que l'incrément du préfixe :

Imaginez un processeur avec registre A est 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 b a été obligé d'être rechargé.Avec l'incrément de préfixe, le compilateur peut simplement incrémenter la valeur et continuer à l'utiliser, éventuellement en évitant de la recharger puisque la valeur souhaitée est déjà dans le registre après l'incrément.Cependant, avec l'incrément postfix, le compilateur doit gérer deux valeurs, l'une l'ancienne et l'autre la valeur incrémentée, ce qui, comme je le montre ci-dessus, entraîne un accès mémoire supplémentaire.

Bien entendu, si la valeur de l'incrément n'est pas utilisée, comme par exemple un seul i++; instruction, le compilateur peut (et génère) simplement une instruction d'incrémentation, quelle que soit l'utilisation du suffixe ou du préfixe.


En passant, j'aimerais mentionner qu'une expression dans laquelle il y a un b++ ne peut pas simplement être converti en un 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, là où vous utilisez b++ à l'intérieur d'une expression que vous ne pouvez pas utiliser ++b, donc même si ++b étaient potentiellement plus efficaces, ce serait tout simplement une erreur.Il y a bien sûr une exception si l'expression le demande (par exemple a = b++ + 1; qui peut être changé en a = ++b;).

Cependant, je préfère toujours le pré-incrémentation...

Je voulais souligner que même dans le cas de l'appel de la fonction Operator++, le compilateur pourra optimiser le temporaire si la fonction est intégrée.Étant donné que l'opérateur ++ est généralement court et souvent implémenté dans l'en-tête, il est susceptible d'être intégré.

Ainsi, pour des raisons 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émentation 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.

De plus, donner à l'optimiseur moins de tâches signifie que le compilateur s'exécute plus rapidement.

Mon C est un peu rouillé, je m'en excuse donc par avance.En termes de vitesse, je peux comprendre les résultats.Mais je ne comprends pas comment les deux fichiers sont sortis avec 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, j'écrit ensuite dans le tableau.Je ne suis pas un expert en assembleur, mais je ne vois tout simplement pas comment le même exécutable serait généré par ces 2 lignes de code différentes.

Juste mes deux cents.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top