398 votes

Y a-t-il une différence de performance entre i ++ et ++ i en C ++?

Nous avons regardé cette réponse pour C dans cette question:

http://stackoverflow.com/questions/24886/is-there-a-performance-difference-between-i-and-i-in-c

Quelle est la réponse pour C ++?

479voto

Mark Harrison Points 77152

[Résumé: Utilisez ++i si vous n'avez pas de raison particulière d'utiliser i++.]

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

Si i est un type simple (pas une instance d'une classe C++), alors la réponse donnée pour C est titulaire, depuis le compilateur génère le code.

Toutefois, si i est une instance d'une classe C++, alors i++ et ++i sont des appels vers l'un de l' operator++ fonctions. Voici une norme paire 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;
}

Car le compilateur n'est pas de la génération de code, mais juste de l'appel d'un operator++ de la fonction, il n'existe aucun moyen pour optimiser loin l' tmp variable et de ses associés constructeur de copie. Si le constructeur de copie est cher, alors cela peut avoir un impact significatif sur les performances.

(Merci à Paul pour s'enquérir au sujet de la différence entre le C et le C++.)

71voto

wilhelmtell Points 25504

Oui. Il y est.

Le ++ opérateur peut ou ne peut pas être définie comme une fonction. Pour les types primitifs (int, double,...), les exploitants se sont intégrés, de sorte que 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.

L'opérateur++(int) de la fonction de doit créer une copie. C'est parce que postfix ++ est prévu pour renvoyer une valeur différente de celle qu'il détient: il doit tenir sa valeur dans une variable temp, incrémenter sa valeur et le retour de la temp. Dans le cas de l'opérateur++(), préfixe ++, il n'est pas nécessaire de créer une copie: l'objet peut incrément de lui-même et ensuite il suffit de retourner lui-même.

Voici une illustration de ce 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 l'opérateur++(int), vous devez créer une copie, et le compilateur ne peut pas faire quoi que ce soit. Quand donné le choix, l'utilisation de l'opérateur++(); de cette façon, vous n'avez pas enregistrer une copie. Il pourrait être important dans le cas de beaucoup d'incréments (grande boucle?) et/ou d'objets volumineux.

57voto

phresnel Points 20082

Voici un indice de référence pour le cas lors de l'incrémentation des opérateurs dans les différentes unités de traduction. Compilateur g++ 4.5.

Ignorer les questions de style pour le moment

// 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;
}

O(n) incrément

Test

// 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

Les résultats (horaires 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

O(1) incrément

Test

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;
}

Elle ne fait rien dans l'incrémentation. Cela simule le cas lors de l'incrémentation a constante de la complexité.

Résultats

Résultats sont très variables:

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

En terme de Performance

Si vous n'avez pas besoin de la valeur précédente, prenez l'habitude d'utiliser les pré-incrémentation. Être compatible même avec builtin types, vous allez en prendre l'habitude et ne pas courir le risque de la souffrance inutile perte de performances si vous remplacez un builtin type avec un type personnalisé.

Sémantique-sage

  • i++ , dit - increment i, I am interested in the previous value, though.
  • ++i , dit - increment i, I am interested in the current value ou increment i, no interest in the previous value. Encore une fois, vous allez en prendre l'habitude, même si vous n'êtes pas maintenant.

Knuth.

L'optimisation prématurée est la racine de tous les maux. Comme il est prématuré pessimization.

22voto

James Sutherland Points 2033

Ce n'est pas tout à fait correct de dire que le compilateur ne peut pas optimiser loin la variable temporaire copie dans le postfix cas. Un test rapide avec VC montre que, au moins, peut 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());
}

Si vous n' ++testFoo ou testFoo++, vous aurez toujours le même code. En fait, sans lire le comte de l'utilisateur, l'optimiseur a obtenu l'ensemble de la chose vers le bas à une constante. Donc ceci:

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

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

A abouti à la suivante:

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

Ainsi, alors qu'il est certainement le cas que postfix version pourrait être plus lent, il se pourrait bien que l'optimiseur va être assez bon pour se débarrasser de la copie temporaire si vous ne l'utilisez pas.

21voto

martjno Points 1024

Il y a quelque chose à ce sujet sur Google c ++ document style de codage.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X