Existe-t-il une différence de performances entre i ++ et ++ i en C ++?

352

Nous avons la question est-il une différence de performance entre i++et ++i en C ?

Quelle est la réponse pour C ++?

Mark Harrison
la source
J'ai repensé car ces deux balises sont le moyen le plus simple de trouver des questions de cette nature. J'ai également passé en revue d'autres qui n'avaient pas d'étiquettes cohésives et leur ai donné des étiquettes cohésives.
George Stocker le
104
Existe-t-il une différence de performances entre l'utilisation de C ++ et ++ C?
new123456
2
Article: Est-il raisonnable d'utiliser l'opérateur d'incrémentation de préfixe ++ au lieu de l'opérateur de suffixe it ++ pour les itérateurs? - viva64.com/en/b/0093

Réponses:

426

[Résumé: à utiliser ++isi vous n'avez pas de raison spécifique d'utiliseri++ .]

Pour C ++, la réponse est un peu plus compliquée.

Si iest 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 iest une instance d'une classe C ++, alors i++et ++ieffectuent des appels à l'une des operator++fonctions. Voici une paire standard de ces fonctions:

Foo& Foo::operator++()   // called for ++i
{
    this->data += 1;
    return *this;
}

Foo Foo::operator++(int ignored_dummy_value)   // called for i++
{
    Foo tmp(*this);   // variable "tmp" cannot be optimized away by the compiler
    ++(*this);
    return tmp;
}

Puisque le compilateur ne génère pas de code, mais appelle simplement une operator++fonction, il n'y a aucun moyen d'optimiser la tmpvariable et son constructeur de copie associé. Si le constructeur de copie coûte cher, cela peut avoir un impact significatif sur les performances.

Mark Harrison
la source
3
Ce que le compilateur peut éviter, c'est la deuxième copie pour renvoyer tmp, en allouant tmp dans l'appelant, via NRVO, comme mentionné par un autre commentaire.
Blaisorblade
7
Le compilateur ne peut-il pas éviter cela si l'opérateur ++ est en ligne?
Eduard - Gabriel Munteanu
16
Oui, si operator ++ est en ligne et que tmp n'est jamais utilisé, il peut être supprimé sauf si le constructeur ou le destructeur de l'objet tmp a des effets secondaires.
Zan Lynx
5
@kriss: la différence entre C et C ++ est qu'en C, vous avez la garantie que l'opérateur sera en ligne, et à ce stade, un optimiseur décent sera en mesure de supprimer la différence; à la place, en C ++, vous ne pouvez pas assumer l'inline - pas toujours.
Blaisorblade
3
J'aurais +1 SI la réponse mentionnait quelque chose sur les classes qui contiennent des pointeurs (qu'ils soient automatiques, intelligents ou primitifs) vers la mémoire allouée dynamiquement (tas), où le constructeur de copie effectue nécessairement des copies complètes. Dans de tels cas, il n'y a pas d'argument, ++ i est peut-être un ordre de grandeur plus efficace que i ++. Leur clé est de prendre l'habitude d'utiliser la pré-incrémentation chaque fois que la sémantique post-incrémentation n'est pas réellement requise par votre algorithme, et vous aurez alors l'habitude d'écrire du code qui, par nature, se prête à une plus grande efficacité, quelle que soit la façon dont bien votre compilateur peut optimiser.
phonetagger
64

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:

struct C
{
    C& operator++();      // prefix
    C  operator++(int);   // postfix

private:

    int i_;
};

C& C::operator++()
{
    ++i_;
    return *this;   // self, no copy created
}

C C::operator++(int ignored_dummy_value)
{
    C t(*this);
    ++(*this);
    return t;   // return a copy
}

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.

wilhelmtell
la source
2
"L'opérateur de pré-incrémentation introduit une dépendance de données dans le code: le CPU doit attendre que l'opération d'incrémentation soit terminée avant que sa valeur puisse être utilisée dans l'expression. Sur un CPU profondément pipeline, cela introduit un blocage. Il n'y a pas de dépendance de données pour l'opérateur de post-incrémentation. " ( Game Engine Architecture (2e édition) ) Donc, si la copie d'un post-incrément n'est pas très gourmande en calcul, elle peut toujours battre le pré-incrément.
Matthias
Dans le code postfix, comment cela fonctionne-t-il? C t(*this); ++(*this); return t;Sur la deuxième ligne, vous incrémentez le pointeur this à droite, alors comment est- til mis à jour si vous l'incrémentez. Les valeurs de cela n'étaient-elles pas déjà copiées t?
rasen58
The operator++(int) function must create a copy.non, ça ne l'est pas. Pas plus de copies queoperator++()
Severin Pappadeux
47

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

// a.cc
#include <ctime>
#include <array>
class Something {
public:
    Something& operator++();
    Something operator++(int);
private:
    std::array<int,PACKET_SIZE> data;
};

int main () {
    Something s;

    for (int i=0; i<1024*1024*30; ++i) ++s; // warm up
    std::clock_t a = clock();
    for (int i=0; i<1024*1024*30; ++i) ++s;
    a = clock() - a;

    for (int i=0; i<1024*1024*30; ++i) s++; // warm up
    std::clock_t b = clock();
    for (int i=0; i<1024*1024*30; ++i) s++;
    b = clock() - b;

    std::cout << "a=" << (a/double(CLOCKS_PER_SEC))
              << ", b=" << (b/double(CLOCKS_PER_SEC)) << '\n';
    return 0;
}

Incrément O (n)

Tester

// b.cc
#include <array>
class Something {
public:
    Something& operator++();
    Something operator++(int);
private:
    std::array<int,PACKET_SIZE> data;
};


Something& Something::operator++()
{
    for (auto it=data.begin(), end=data.end(); it!=end; ++it)
        ++*it;
    return *this;
}

Something Something::operator++(int)
{
    Something ret = *this;
    ++*this;
    return ret;
}

Résultats

Résultats (les temps sont en secondes) avec g ++ 4.5 sur une machine virtuelle:

Flags (--std=c++0x)       ++i   i++
-DPACKET_SIZE=50 -O1      1.70  2.39
-DPACKET_SIZE=50 -O3      0.59  1.00
-DPACKET_SIZE=500 -O1    10.51 13.28
-DPACKET_SIZE=500 -O3     4.28  6.82

Incrément O (1)

Tester

Prenons maintenant le fichier suivant:

// c.cc
#include <array>
class Something {
public:
    Something& operator++();
    Something operator++(int);
private:
    std::array<int,PACKET_SIZE> data;
};


Something& Something::operator++()
{
    return *this;
}

Something Something::operator++(int)
{
    Something ret = *this;
    ++*this;
    return ret;
}

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:

Flags (--std=c++0x)       ++i   i++
-DPACKET_SIZE=50 -O1      0.05   0.74
-DPACKET_SIZE=50 -O3      0.08   0.97
-DPACKET_SIZE=500 -O1     0.05   2.79
-DPACKET_SIZE=500 -O3     0.08   2.18
-DPACKET_SIZE=5000 -O3    0.07  21.90

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++ dit increment i, I am interested in the previous value, though .
  • ++idit increment i, I am interested in the current valueou increment 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.

phresnel
la source
1
Test intéressant. Maintenant, près de deux ans et demi plus tard, gcc 4.9 et Clang 3.4 montrent une tendance similaire. Clang est un peu plus rapide avec les deux, mais la disparité entre pré et postfix est pire que gcc.
chaussettes à mâcher
Ce que j'aimerais vraiment voir, c'est un exemple concret où ++ i / i ++ fait une différence. Par exemple, cela fait-il une différence sur l'un des itérateurs std?
Jakob Schou Jensen
@JakobSchouJensen: C'étaient plutôt des exemples du monde réel. Considérez une grande application, avec des structures d'arbres complexes (par exemple kd-arbres, quad-arbres) ou de grands conteneurs utilisés dans les modèles d'expression (afin de maximiser le débit de données sur le matériel SIMD). Si cela fait une différence, je ne sais pas vraiment pourquoi on pourrait revenir au post-incrément pour des cas spécifiques si cela n'est pas nécessaire sémantiquement.
Sebastian Mach
@phresnel: Je ne pense pas que operator ++ soit dans votre quotidien un modèle d'expression - en avez-vous un exemple concret? L'usage typique de operator ++ concerne les entiers et les itérateurs. C'était je pense qu'il serait intéressant de savoir s'il y a une différence (il n'y a pas de différence sur les entiers bien sûr - mais les itérateurs).
Jakob Schou Jensen
@JakobSchouJensen: Pas d'exemple commercial réel, mais certaines applications de calcul de nombre où vous comptez des trucs. Wrt iterators, consider a ray tracer that is writing in idiomatic C ++ style, and you have a itator for depth-first traversal, such that 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 exemple parent node, child node, indexet d'autres choses comme ça. Dans l'ensemble, ma position est, même si seuls quelques exemples existent, ...
Sebastian Mach
20

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:

#include <stdio.h>

class Foo
{
public:

    Foo() { myData=0; }
    Foo(const Foo &rhs) { myData=rhs.myData; }

    const Foo& operator++()
    {
        this->myData++;
        return *this;
    }

    const Foo operator++(int)
    {
        Foo tmp(*this);
        this->myData++;
        return tmp;
    }

    int GetData() { return myData; }

private:

    int myData;
};

int main(int argc, char* argv[])
{
    Foo testFoo;

    int count;
    printf("Enter loop count: ");
    scanf("%d", &count);

    for(int i=0; i<count; i++)
    {
        testFoo++;
    }

    printf("Value: %d\n", testFoo.GetData());
}

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:

for(int i=0; i<10; i++)
{
    testFoo++;
}

printf("Value: %d\n", testFoo.GetData());

Résultat:

00401000  push        0Ah  
00401002  push        offset string "Value: %d\n" (402104h) 
00401007  call        dword ptr [__imp__printf (4020A0h)] 

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.

James Sutherland
la source
8
Vous avez oublié de noter le point important qu'ici tout est en ligne. Si les définitions des opérateurs ne sont pas disponibles, la copie effectuée dans le code hors ligne ne peut être évitée; en insérant l'optim est assez évident, donc tout compilateur le fera.
Blaisorblade
14

Le guide de style Google C ++ dit:

Préincrément et prédécrément

Utilisez le préfixe (++ i) des opérateurs d'incrémentation et de décrémentation avec des itérateurs et d'autres objets de modèle.

Définition: Lorsqu'une variable est incrémentée (++ i ou i ++) ou décrémentée (--i ou i--) et que la valeur de l'expression n'est pas utilisée, il faut décider de pré-incrémenter (décrémenter) ou de post-incrémenter (décrémenter).

Avantages: Lorsque la valeur de retour est ignorée, le formulaire "pré" (++ i) n'est jamais moins efficace que le formulaire "post" (i ++), et est souvent plus efficace. En effet, après l'incrémentation (ou la décrémentation), une copie de i doit être effectuée, qui est la valeur de l'expression. Si i est un itérateur ou un autre type non scalaire, la copie de i pourrait être coûteuse. Étant donné que les deux types d'incrément se comportent de la même manière lorsque la valeur est ignorée, pourquoi ne pas simplement toujours pré-incrémenter?

Inconvénients: La tradition s'est développée, en C, d'utiliser le post-incrément lorsque la valeur d'expression n'est pas utilisée, en particulier dans les boucles for. Certains trouvent que le post-incrément est plus facile à lire, car le "sujet" (i) précède le "verbe" (++), tout comme en anglais.

Décision: Pour les valeurs scalaires simples (non-objet), il n'y a aucune raison de préférer une forme et nous autorisons l'une ou l'autre. Pour les itérateurs et autres types de modèles, utilisez la pré-incrémentation.

martjno
la source
1
"Décision: pour les valeurs scalaires simples (non-objet), il n'y a aucune raison de préférer une forme et nous autorisons l'une ou l'autre. Pour les itérateurs et autres types de modèles, utilisez la pré-incrémentation."
Nosredna
2
Eh, ..., et qu'est-ce que c'est?
Sebastian Mach
Le lien mentionné dans la réponse est actuellement rompu
karol
4

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
4

@Ketan

... soulève des détails négligés concernant l'intention par rapport aux performances. Il y a des moments où nous voulons utiliser iter ++ au lieu de ++ iter.

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 forboucles). 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, ++ic'est plus naturel, mais mon expérience me dit que je suis en minorité et que l'utilisation i++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.

Motti
la source
4
@Motti: (en plaisantant) Le nom C ++ est logique si vous vous souvenez de Bjarne Stroustrup C ++ initialement codé comme un précompilateur générant un programme C. Par conséquent, C ++ a renvoyé une ancienne valeur C. Ou il se peut que le C ++ soit quelque peu défectueux sur le plan conceptuel depuis le début.
kriss
4
  1. ++ i - plus rapide sans utiliser la valeur de retour
  2. i ++ - plus rapide en utilisant la valeur de retour

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.

Hans Malherbe
la source
3

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. :-)

0124816
la source
3

La différence de performances entre ++iet i++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 utiliseront intcomme s'il s'agissait d'un struct.

++iincré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:

int& int::operator++() { 
     return *this += 1;
}

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:

int int::operator++(int& _Val) {
    int _Original = _Val;
    _Val += 1;
    return _Original;
}

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 structou classest 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.

DragonLord
la source
1

@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)

Mike Dunlavey
la source
1

@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

Mat Noguchi
la source
1
Ce n'est pas pertinent. NRVO évite d'avoir à copier t dans "CC :: operator ++ (int)" vers l'appelant, mais i ++ copiera toujours l'ancienne valeur sur la pile de l'appelant. Sans NRVO, i ++ crée 2 copies, une vers t et une vers l'appelant.
Blaisorblade
0

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.

Josh
la source
3
Désolé, mais cela me dérange. Qui a dit que c'était une "bonne habitude", alors que cela n'avait presque jamais d'importance? Si les gens veulent faire partie de leur discipline, c'est bien, mais distinguons les raisons importantes des questions de goût personnel.
Mike Dunlavey
@MikeDunlavey ok, alors quel côté utilisez-vous normalement quand cela n'a pas d'importance? xD c'est l'un ou l'autre n'est-ce pas! le post ++ (si vous l'utilisez avec le sens général. mettez-le à jour, retournez l'ancien) est complètement inférieur à ++ pre (mettez-le à jour, retournez) il n'y a jamais de raison de vouloir avoir moins de performances. dans le cas où vous voudriez le mettre à jour après, le programmeur ne fera même pas du tout le post ++. ne perdez pas de temps à copier quand nous l'avons déjà. le mettre à jour après l'avoir utilisé. puis les compilateurs ayant le bon sens que vous vouliez.
Puddle
@Puddle: Quand j'entends ceci: "il n'y a jamais aucune raison pour laquelle vous voudriez avoir moins de performances" Je sais que j'entends "penny sage - pound foolish". Vous devez avoir une appréciation des ampleurs impliquées. Ce n'est que si cela représente plus de 1% du temps impliqué que vous devriez même y réfléchir. Habituellement, si vous y pensez, il y a des millions de fois plus de problèmes que vous ne considérez pas , et c'est ce qui rend le logiciel beaucoup plus lent qu'il ne pourrait l'être.
Mike Dunlavey
@MikeDunlavey a régurgité des bêtises pour satisfaire votre ego. vous essayez de ressembler à un moine tout sage, mais vous ne dites rien. les grandeurs impliquées ... si seulement plus de 1% du temps vous devriez vous en soucier ... xD dribble absolu. s'il est inefficace, il vaut la peine de le savoir et de le réparer. nous réfléchissons ici pour cette raison exacte! nous ne sommes pas préoccupés par combien nous pouvons gagner de cette connaissance. et quand j'ai dit que vous ne voudriez pas moins de performances, allez-y, expliquez alors un putain de scénario. MR WISE!
Flaque d'
0

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:

#include <stdio.h>

int main()
{
    int a = 0;
    a++;
    int b = 0;
    ++b;
    return 0;
}

Produisez l'assemblage suivant:

 0x0000000100000f24 <main+0>: push   %rbp
 0x0000000100000f25 <main+1>: mov    %rsp,%rbp
 0x0000000100000f28 <main+4>: movl   $0x0,-0x4(%rbp)
 0x0000000100000f2f <main+11>:    incl   -0x4(%rbp)
 0x0000000100000f32 <main+14>:    movl   $0x0,-0x8(%rbp)
 0x0000000100000f39 <main+21>:    incl   -0x8(%rbp)
 0x0000000100000f3c <main+24>:    mov    $0x0,%eax
 0x0000000100000f41 <main+29>:    leaveq 
 0x0000000100000f42 <main+30>:    retq

Vous voyez que pour a ++ et b ++ c'est un mnémonique incl, donc c'est la même opération;)

Geoffroy
la source
C'est C, tandis que OP a demandé à C ++. En C c'est pareil. En C ++, le plus rapide est ++ i; en raison de son objet. Cependant, certains compilateurs peuvent optimiser l'opérateur post-incrémentation.
Wiggler Jtag
0

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.

Blaisorblade
la source
Je vois que vous travaillez sur un doctorat. avec un intérêt pour l'optimisation du compilateur et des choses de ce genre. C'est génial, mais n'oubliez pas que le milieu universitaire est une chambre d'écho, et le bon sens est souvent laissé à l'extérieur, au moins dans CS Vous pourriez être intéressé par ceci: stackoverflow.com/questions/1303899/…
Mike Dunlavey
Je n'ai jamais trouvé ++iplus ennuyeux que i++(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"
Sebastian Mach
strncpyservi 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.
MM
@MattMcNabb: le nom de fichier à 8 caractères n'était-il pas une exclusivité MS-DOS? C a été inventé avec Unix. Quoi qu'il en soit, même si strncpy avait un point, le manque de strlcpy n'était pas entièrement justifié: même le C d'origine avait des tableaux que vous ne devriez pas déborder, qui avaient besoin de strlcpy; tout au plus ne manquaient-ils que des attaquants désireux d'exploiter les bogues. Mais on ne peut pas dire que prévoir ce problème était trivial, donc si je réécrivais mon message, je n'utiliserais pas le même ton.
Blaisorblade
@Blaisorblade: Si je me souviens bien, les premiers noms de fichiers UNIX étaient limités à 14 caractères. Cette absence strlcpy()était justifiée par le fait qu'elle n'avait pas encore été inventée.
Keith Thompson
-1

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

#include <iostream>

class Data {
    private: class DataIncrementer {
        private: Data& _dref;

        public: DataIncrementer(Data& d) : _dref(d) {}

        public: ~DataIncrementer() {
            ++_dref;
        }
    };

    private: int _data;

    public: Data() : _data{0} {}

    public: Data(int d) : _data{d} {}

    public: Data(const Data& d) : _data{ d._data } {}

    public: Data& operator=(const Data& d) {
        _data = d._data;
        return *this;
    }

    public: ~Data() {}

    public: Data& operator++() { // prefix
        ++_data;
        return *this;
    }

    public: Data operator++(int) { // postfix
        DataIncrementer t(*this);
        return *this;
    }

    public: operator int() {
        return _data;
    }
};

int
main() {
    Data d(1);

    std::cout <<   d << '\n';
    std::cout << ++d << '\n';
    std::cout <<   d++ << '\n';
    std::cout << d << '\n';

    return 0;
}

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.

Severin Pappadeux
la source
-5

++i est plus rapide que i++ parce qu'il ne retourne pas une ancienne copie de la valeur.

C'est aussi plus intuitif:

x = i++;  // x contains the old value of i
y = ++i;  // y contains the new value of i 

Cet exemple C imprime "02" au lieu du "12" auquel vous pourriez vous attendre:

#include <stdio.h>

int main(){
    int a = 0;
    printf("%d", a++);
    printf("%d", ++a);
    return 0;
}

Idem pour C ++ :

#include <iostream>
using namespace std;

int main(){
    int a = 0;
    cout << a++;
    cout << ++a;
    return 0;
}
Cees Timmerman
la source