Est-il une différence de performance entre i++ et ++i en C?

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

395
demandé sur Mark Harrison 2008-08-24 10:48:23
la source

13 ответов

résumé: Aucun.

i++ pourrait être potentiellement plus lent que ++i , puisque l'ancienne valeur de i pourrait devoir être économisé pour une utilisation ultérieure, mais dans la pratique tout moderne les compilateurs optimiseront cela.

On peut 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 aussi obtenir le assembleur généré:

$ gcc -c i++.c ++i.c
$ gcc -S i++.c ++i.c

et nous pouvons voir que les deux fichiers d'objet et d'assemblage 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
335
répondu Mark Harrison 2015-10-06 22:08:24
la source

à Partir de l'Efficacité par rapport à l'intention de par 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:

donc la question qu'il faut se poser n'est pas laquelle de ces deux opérations est la plus rapide, c'est laquelle de ces deux opérations exprime le plus exactement ce que vous essayez d'accomplir. Je soumets que si vous n'utilisez pas la valeur de l'expression, il n'y a jamais une raison d'utiliser i++ au lieu de ++i , parce qu'il n'y a jamais une raison de copier la valeur d'une variable, incrémenter la variable, puis 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 exprime correctement mon intention.

92
répondu Sébastien RoccaSerra 2015-10-06 22:20:01
la source

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 pas de différence mesurable.

cependant si i est un type complexe alors vous pouvez bien trouver une différence mesurable. Pour i++ vous devez faire une copie de votre classe avant l'incrémentation. Selon ce qui est impliqué dans une copie qu'il pourrait en effet être plus lent puisqu'avec ++it 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 avez la possibilité de retourner une référence au lieu d'une valeur. Encore une fois, selon ce qui est impliqué dans la fabrication d'une copie de votre objet, cela pourrait être plus lente.

un exemple concret d'une telle possibilité serait l'utilisation d'itérateurs. Copier un itérateur est peu probable d'être goulot d'étranglement dans votre application, mais c'est toujours une bonne pratique de prendre l'habitude d'utiliser ++i au lieu de i++ , où le résultat n'est pas affecté.

42
répondu Andrew Grant 2015-10-06 22:25:20
la source

Voici une observation supplémentaire si vous êtes inquiet de micro optimisation. Les boucles de décrémentation peuvent "probablement" être plus efficaces que les boucles d'incrémentation (en fonction de l'architecture des jeux d'instructions, par exemple ARM), étant donné:

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

sur chaque boucle vous aurez une instruction chacun pour:

  1. ajout de 1 à i .
  2. comparez si i est inférieur à 100 .
  3. branche conditionnelle si i est inférieur à 100 .

attendu qu'une boucle Décroissante:

for (i = 100; i != 0; i--)

la boucle aura une instruction pour chacun des éléments suivants:

  1. Decrement i , définissant l'indicateur de statut du registre CPU.
  2. branche conditionnelle dépendant de L'état du registre CPU ( Z==0 ).

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

D'après le guide de développement du système ARM.

16
répondu tonylo 2015-10-06 21:59:26
la source

en Prenant une feuille de Scott Meyers, Plus Effective c++ Article 6: la Distinction entre le préfixe et le suffixe formes d'incrémentation et de décrémentation opérations .

la version préfixe est toujours préférée à la version postfix en ce qui concerne les objets, en particulier en ce qui concerne les itérateurs.

La raison de cette si vous regardez le motif 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;
}

dans cet exemple, il est facile de voir comment le préfixe de l'opérateur sera toujours plus efficace que la postfix. En raison de la nécessité pour un objet temporaire dans l'utilisation de postfix.

C'est pourquoi, lorsque vous voyez des exemples d'utilisation des itérateurs ils utilisent toujours le préfixe version.

mais comme vous le soulignez pour int's il n'y a effectivement aucune différence en raison de l'optimisation du compilateur qui peut avoir lieu.

14
répondu JProgrammer 2008-08-25 08:29:02
la source

brève réponse:

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 omettent de mentionner, c'est que la différence entre ++i et i++ n'a de sens que dans l'expression elle est trouver.

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 lui. Ainsi le seul code machine généré est "increase i par 1 " et il est bien défini comment ceci est séquencé par rapport au reste du programme. Donc si vous le changiez en préfixe ++ , cela n'aurait pas la moindre importance, vous obtiendriez tout de même le code machine "augmenter i par 1 ".

les différences entre ++i et i++ ne concernent que les expressions array[i++] = x; et array[++i] = x; . Certains peuvent argumenter et dire que le postfix sera plus lent dans de telles opérations parce que le registre où i réside doit être rechargé plus tard. Mais alors notez que le compilateur est libre de commander vos instructions de n'importe quelle manière qui lui plaît, tant qu'il ne "brise pas le comportement du machine abstraite", comme l'appelle la norme C.

donc alors que vous pouvez supposer que array[i++] = x; est traduit en code machine comme:

  • valeur en magasin de i dans le registre A.
  • adresse du magasin de l'ensemble dans le registre B.
  • Ajouter A et B, stocker les résultats dans A.
  • à cette nouvelle adresse représentée par A, stocker la valeur de X.
  • valeur en magasin de i dans le registre a / / inefficace parce que l'instruction supplémentaire ici, nous l'avons déjà fait une fois.
  • Incrementary register A.
  • Store inscrivez-vous à Un i .

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

  • valeur en magasin de i dans le registre A.
  • adresse du magasin de l'ensemble dans le registre B.
  • Ajouter A et B, conserver les résultats dans B.
  • Incrementary register A.
  • Store inscrivez-vous à Un i .
  • ... // le reste du code.

juste parce que vous en tant que programmeur C êtes formé à penser que le postfix ++ arrive à la fin, le code machine n'a pas à être commandé de cette façon.

il n'y a donc pas de différence entre préfixe et postfix ++ dans C. Maintenant, ce que vous en tant que programmeur C devrait être varier, ce sont les gens qui utilisent de manière incohérente le préfixe dans certains cas et postfix dans d'autres cas, sans aucune raison. Cela donne à penser qu'ils ne sont pas certains de la façon dont C fonctionne ou qu'ils ont une mauvaise connaissance de la langue. C'est toujours un mauvais signe, cela suggère à son tour qu'ils prennent d'autres décisions DOUTEUSES dans leur programme, basées sur la superstition ou "dogmes religieux".

"Prefix ++ est toujours plus rapide"est en effet un tel faux dogme qui est commun parmi les programmeurs potentiels C.

11
répondu Lundin 2016-02-26 10:23:50
la source

s'il vous Plaît ne laissez pas la question de "qui est plus rapide" être le facteur décisif de ce qui à utiliser. Les Chances sont que vous n'allez pas beaucoup, et d'ailleurs, programmeur temps de lecture est beaucoup plus cher que la machine du temps.

utilisez celle qui a le plus de sens pour l'homme qui lit le code.

10
répondu Andy Lester 2008-09-20 09:09:18
la source

tout d'abord: la différence entre i++ et ++i est neglegible en C.


aux détails.

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

En C++, ++i est plus efficace iff i est en quelques sorte un objet avec une surcharge opérateur d'incrémentation.

pourquoi?

Dans ++i , le objet est d'abord incrémenté, et peut ensuite passer comme une 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 foo() est appelé, mais l'ancienne valeur doit être passée à foo() . Par conséquent, le compilateur est forcé de faire une copie de i avant d'exécuter l'opérateur d'incrémentation sur l'original. Supplémentaires constructeur/destructeur appels sont la mauvaise partie.

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 aussi rapide, non? Aucun. Ils sont pratiquement aussi rapides, mais il peut y avoir de petites différences, que la plupart des autres répondants ont eu le mauvais chemin autour.

comment i++ peut-il être plus rapide?

Le point de dépendances de données. Si la valeur doit être chargé à partir de la mémoire, deux opérations doivent être fait avec elle, l'incrémentation, et de l'utiliser. Avec ++i , l'incrémentation doit être fait 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 à la opération incrémentielle. La différence est au plus un cycle CPU, donc il est vraiment nglegible, mais il est là. Et c'est le contraire que beaucoup s'attendaient à voir.

9
répondu cmaster 2014-06-04 22:34:44
la source

@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) le fait, 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.

N'assume jamais que le compilateur l'obtient 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:

Toujours préférer ++i sur i++.

7
répondu Andreas 2009-02-09 18:40:45
la source

en C, le compilateur peut généralement les optimiser pour qu'elles soient les mêmes 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 postfix sémantique, il est préférable d'utiliser le préfixe de l'opérateur.

4
répondu Kristopher Johnson 2008-08-24 18:29:01
la source

je peux penser à une situation où 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 ceci).

imaginez maintenant le programme suivant et leur traduction en un assemblage hypothétique:

incrément du 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 Postfix:

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]

noter comment la valeur de b a été forcée à être rechargée. Avec l'incrément de préfixe, le compilateur peut simplement incrémenter la valeur et continuer à l'utiliser, éventuellement éviter de la recharger puisque la valeur désirée est déjà dans le Registre après l'incrément. Cependant, avec l'incrément de postfix, le compilateur doit traiter deux valeurs, l'une ancienne et l'autre incrémentée qui comme je le montre ci-dessus résulte en une mémoire de plus accès.

bien sûr, si la valeur de l'incrément n'est pas utilisée, comme une simple déclaration i++; , le compilateur peut (et produit) simplement générer une instruction d'incrément indépendamment de l'utilisation du postfix ou du préfixe.


comme note latérale, je tiens à mentionner qu'une expression dans laquelle il y a un b++ ne peut pas simplement être convertie en une avec ++b sans aucun effort supplémentaire (par exemple en ajoutant un - 1 ). Donc comparer les deux si elles font partie d'une certaine expression n'est pas vraiment valable. Souvent, lorsque vous utilisez b++ dans une expression , vous ne pouvez pas utiliser ++b , donc même si ++b était potentiellement plus efficace, ce serait tout simplement mal. L'Exception est bien sûr si l'expression est suppliante pour elle (par exemple a = b++ + 1; qui peut être changé en a = ++b; ).

4
répondu Shahbaz 2014-06-04 21:57:51
la source

je préfère toujours pré-incrément, cependant ...

je voulais faire remarquer que même dans le cas d'un appel à la fonction operator++, le compilateur sera capable d'optimiser le temporaire si la fonction est raccourcie. Depuis l'opérateur++ est généralement court et souvent mis en œuvre dans l'en-tête, il est susceptible d'être insérée.

donc, pour des raisons pratiques, il n'y a probablement pas beaucoup de différence entre la performance des deux formes. Cependant, je préfère toujours la pré-incrémentation depuis, il semble préférable d'exprimer directement ce que j'essaie de dire, plutôt que de compter sur l'optimiseur de figure.

aussi, donner à l'optmizer moins à faire signifie probablement que le compilateur court plus vite.

2
répondu 2008-10-16 15:28:13
la source

mon C est un peu rouillé, alors je m'excuse à l'avance. Sur le plan de la vitesse, je peux comprendre les résultats. Mais je ne comprends pas comment les deux fichiers sont sortis du même hachage MD5. Peut-être une boucle for s'exécute le même, mais ne pas les 2 lignes de code générer différents assemblée?

myArray[i++] = "hello";

vs

myArray[++i] = "hello";

le premier écrit la valeur au tableau, puis increments I. La deuxième incréments j'écris alors au tableau. Je ne suis pas expert en assemblage, mais je ne vois pas comment le même exécutable serait généré par ces deux lignes de code différentes.

juste mes deux cents.

0
répondu Jason Z 2008-08-24 18:22:47
la source