Les énumérations C ++ sont-elles plus lentes à utiliser que les entiers?

C’est vraiment un problème simple:

Je programme un programme Go. Dois-je représenter le forum avec un QVector ou un QVector

 enum Player { EMPTY = 0, BLACK = 1, WHITE = 2 }; 

Je suppose que, bien sûr, l’utilisation de Player à la place d’entiers sera plus lente. Mais je me demande combien de temps, car je crois que l’utilisation d’ enum est un meilleur codage.

J’ai fait quelques tests concernant l’atsortingbution et la comparaison des joueurs (par opposition à l’ int )

 QVector vec; vec.resize(10000000); int size = vec.size(); for(int i =0; i<size; ++i) { vec[i] = 0; } for(int i =0; i<size; ++i) { bool b = (vec[i] == 1); } QVector vec2; vec2.resize(10000000); int size = vec2.size(); for(int i =0; i<size; ++i) { vec2[i] = EMPTY; } for(int i =0; i<size; ++i) { bool b = (vec2[i] == BLACK); } 

Fondamentalement, ce n’est que 10% plus lent. Y a-t-il autre chose que je devrais savoir avant de continuer?

Merci!

Edit: La différence de 10% n’est pas le fruit de mon imagination, elle semble spécifique à Qt et QVector. Quand j’utilise std :: vector, la vitesse est la même

Les enums sont complètement résolus au moment de la compilation (constantes enum en tant que littéraux entiers, variables enum en tant que variables entières), il n’y a pas de pénalité de vitesse dans leur utilisation.

En général, l’énumération moyenne n’aura pas un type sous-jacent plus grand que l’ int (sauf si vous y mettez de très grandes constantes); en fait, au § 7.2.2, il est explicitement dit:

Le type sous-jacent d’une énumération est un type intégral qui peut représenter toutes les valeurs d’énumérateur définies dans l’énumération. Le type intégral utilisé comme type sous-jacent pour une énumération est défini par l’implémentation, sauf que le type sous-jacent ne doit pas être plus grand que int moins que la valeur d’un énumérateur ne puisse tenir dans int ou unsigned int .

Vous devez utiliser des énumérations lorsque cela est approprié car elles facilitent généralement la lecture et la maintenance du code (avez-vous déjà essayé de déboguer un programme contenant des “nombres magiques”?: :S ).

En ce qui concerne vos résultats: votre méthodologie de test ne tient probablement pas compte des fluctuations de vitesse normales que vous obtenez lorsque vous exécutez du code sur des machines “normales” 1 ; Avez-vous essayé de faire le test plus de 100 fois et de calculer la moyenne et l’écart type de vos temps? Les résultats doivent être compatibles: la différence entre les moyennes ne doit pas être supérieure à 1 ou 2 fois le RSS 2 des deux écarts types (en supposant, comme d’habitude, une dissortingbution gaussienne pour les fluctuations).

Une autre vérification que vous pouvez faire est de comparer le code assembleur généré (avec g ++, vous pouvez l’obtenir avec le commutateur -S ).


  1. Sur des PC “normaux”, vous rencontrez des fluctuations indéterminées en raison d’autres tâches en cours d’exécution, de l’état du cache / RAM / VM, …
  2. Racine Somme au carré, la racine carrée de la sum des écarts-types au carré.

En général, utiliser un enum ne devrait absolument pas faire de différence en termes de performances. Comment avez-vous testé cela?

Je viens de faire des tests moi-même. Les différences sont du bruit pur.

Tout à l’heure, j’ai compilé les deux versions à l’assembleur. Voici la fonction principale de chacun:

int

 LFB1778: pushl %ebp LCFI11: movl %esp, %ebp LCFI12: subl $8, %esp LCFI13: movl $65535, %edx movl $1, %eax call __Z41__static_initialization_and_destruction_0ii leave ret 

Joueur

 LFB1774: pushl %ebp LCFI10: movl %esp, %ebp LCFI11: subl $8, %esp LCFI12: movl $65535, %edx movl $1, %eax call __Z41__static_initialization_and_destruction_0ii leave ret 

Il est dangereux de baser toute déclaration concernant la performance sur des micro-benchmarks. Il y a trop de facteurs externes qui faussent les données.

Enums ne devrait pas être plus lent. Ils sont implémentés sous forme d’entiers.

si vous utilisez Visual Studio par exemple, vous pouvez créer un projet simple où vous avez

  a=Player::EMPTY; 

et si vous cliquez avec le bouton droit sur “aller au déassembly”, le code sera

 mov dword ptr [a],0 

Le compilateur remplace donc la valeur de l’énumération et ne génère normalement aucune surcharge.

Eh bien, j’ai fait quelques tests et il n’y avait pas beaucoup de différence entre les formes entières et entières. J’ai également ajouté une forme de caractère qui était toujours d’environ 6% plus rapide (ce qui n’est pas surprenant car elle utilise moins de mémoire). Ensuite, j’ai juste utilisé un tableau de caractères plutôt qu’un vecteur et c’était 300% plus rapide! Comme on ne nous a pas donné ce qu’est QVector, cela pourrait être un wrapper pour un tableau plutôt que le std :: vector que j’ai utilisé.

Voici le code que j’ai utilisé, compilé en utilisant les options de publication standard dans Dev Studio 2005. Notez que j’ai modifié la boucle temporisée avec une petite quantité car le code dans la question pourrait être optimisé à rien (vous devriez vérifier le code de l’assemblage) .

 #include  #include  #include  using namespace std; enum Player { EMPTY = 0, BLACK = 1, WHITE = 2 }; template  LONGLONG TimeFunction () { vector  vec; vec.resize (10000000); size_t size = vec.size (); for (size_t i = 0 ; i < size ; ++i) { vec [i] = static_cast  (rand () % 3); } LARGE_INTEGER start, end; QueryPerformanceCounter (&start); for (size_t i = 0 ; i < size ; ++i) { if (vec [i] == search) { break; } } QueryPerformanceCounter (&end); return end.QuadPart - start.QuadPart; } LONGLONG TimeArrayFunction () { size_t size = 10000000; char *vec = new char [size]; for (size_t i = 0 ; i < size ; ++i) { vec [i] = static_cast  (rand () % 3); } LARGE_INTEGER start, end; QueryPerformanceCounter (&start); for (size_t i = 0 ; i < size ; ++i) { if (vec [i] == 10) { break; } } QueryPerformanceCounter (&end); delete [] vec; return end.QuadPart - start.QuadPart; } int main () { cout << " Char form = " << TimeFunction  () << endl; cout << "Integer form = " << TimeFunction  () << endl; cout << " Player form = " << TimeFunction  (10)> () << endl; cout << " Array form = " << TimeArrayFunction () << endl; } 

Le compilateur doit convertir enum en entiers. Ils sont intégrés à la compilation, donc une fois que votre programme est compilé, il est censé être exactement le même que si vous utilisiez les entiers eux-mêmes.

Si vos tests produisent des résultats différents, il se peut que quelque chose se passe avec le test lui-même. Soit ça, soit votre compilateur se comporte bizarrement.

Cela dépend de l’implémentation, et il est tout à fait possible que les énumérations et les ints aient des performances différentes et un code d’assemblage identique ou différent, bien qu’il s’agisse probablement d’un signe d’un compilateur sous-optimal. certains moyens d’obtenir des différences sont les suivants:

  • QVector peut être spécialisé sur votre type enum pour faire quelque chose de surprenant.
  • Enum n’est pas compilé dans int mais dans “un type entier non plus grand que int”. QVector de int peut être spécialisé différemment de QVector de some_integral_type.
  • même si QVector n’est pas spécialisé, le compilateur peut mieux aligner les ints en mémoire que l’alignement de some_integral_type, ce qui conduit à un plus grand taux d’échec du cache lorsque vous passez en boucle sur le vecteur des enums ou some_integral_type.