Quelle est la meilleure pratique pour utiliser une switch
instruction par rapport à l'utilisation d'une if
instruction pour 30 unsigned
énumérations où environ 10 ont une action attendue (qui est actuellement la même action). Les performances et l'espace doivent être pris en compte mais ne sont pas critiques. J'ai résumé l'extrait alors ne me détestez pas pour les conventions de dénomination.
switch
déclaration:
// numError is an error enumeration type, with 0 being the non-error case
// fire_special_event() is a stub method for the shared processing
switch (numError)
{
case ERROR_01 : // intentional fall-through
case ERROR_07 : // intentional fall-through
case ERROR_0A : // intentional fall-through
case ERROR_10 : // intentional fall-through
case ERROR_15 : // intentional fall-through
case ERROR_16 : // intentional fall-through
case ERROR_20 :
{
fire_special_event();
}
break;
default:
{
// error codes that require no additional action
}
break;
}
if
déclaration:
if ((ERROR_01 == numError) ||
(ERROR_07 == numError) ||
(ERROR_0A == numError) ||
(ERROR_10 == numError) ||
(ERROR_15 == numError) ||
(ERROR_16 == numError) ||
(ERROR_20 == numError))
{
fire_special_event();
}
Réponses:
Utilisez le commutateur.
Dans le pire des cas, le compilateur générera le même code qu'une chaîne if-else, donc vous ne perdez rien. En cas de doute, placez les cas les plus courants en premier dans l'instruction switch.
Dans le meilleur des cas, l'optimiseur peut trouver un meilleur moyen de générer le code. Les choses courantes qu'un compilateur fait est de construire un arbre de décision binaire (enregistre les comparaisons et les sauts dans le cas moyen) ou simplement de construire une table de sauts (fonctionne sans comparaison du tout).
la source
if
-else
chaînes dans la programmation méta modèle, et la difficulté de générer desswitch
case
chaînes, que la cartographie peut devenir plus important. (et oui, commentaire ancien, mais le web est pour toujours, ou du moins jusqu'à mardi prochain)Pour le cas particulier que vous avez fourni dans votre exemple, le code le plus clair est probablement:
Évidemment, cela déplace simplement le problème vers une autre zone du code, mais vous avez maintenant la possibilité de réutiliser ce test. Vous avez également plus d'options pour le résoudre. Vous pouvez utiliser std :: set, par exemple:
Je ne suggère pas que c'est la meilleure implémentation de RequiertSpecialEvent, mais simplement que c'est une option. Vous pouvez toujours utiliser un commutateur ou une chaîne if-else, ou une table de recherche, ou une manipulation de bits sur la valeur, peu importe. Plus votre processus de décision devient obscur, plus vous tirerez de la valeur de l'avoir dans une fonction isolée.
la source
const std::bitset<MAXERR> specialerror(initializer);
utilisez-le avecif (specialerror[numError]) { fire_special_event(); }
. Si vous voulez vérifier les limites,bitset::test(size_t)
lèvera une exception sur les valeurs hors limites. (bitset::operator[]
ne vérifie pas la plage). cplusplus.com/reference/bitset/bitset/test . Cela surpassera probablement une implémentation de table de saut générée par le compilateurswitch
, esp. dans le cas non spécial où il s'agira d'une seule branche non prise.std::set
, j'ai pensé souligner que c'est probablement un mauvais choix. Il s'avère que gcc compile déjà le code de l'OP pour tester un bitmap dans un immédiat 32 bits. godbolt: goo.gl/qjjv0e . gcc 5.2 le fera même pour laif
version. De plus, gcc plus récent utilisera l'instruction de test de bitsbt
au lieu de déplacer pour mettre un1
peu au bon endroit et utilisertest reg, imm32
.Le changement est plus rapide.
Essayez simplement if / else-ing 30 valeurs différentes dans une boucle, et comparez-le au même code en utilisant switch pour voir à quel point le switch est plus rapide.
Maintenant, le commutateur a un vrai problème : le commutateur doit connaître au moment de la compilation les valeurs à l'intérieur de chaque cas. Cela signifie que le code suivant:
ne compilera pas.
La plupart des gens utiliseront alors définit (Aargh!), Et d'autres déclareront et définiront des variables constantes dans la même unité de compilation. Par exemple:
Donc, au final, le développeur doit choisir entre "vitesse + clarté" ou "couplage de code".
(Non pas qu'un commutateur ne peut pas être écrit pour être déroutant comme l'enfer ... La plupart des commutateurs que je vois actuellement sont de cette catégorie "déroutante" "... Mais c'est une autre histoire ...)
.
la source
Le compilateur l'optimisera de toute façon - optez pour le commutateur car c'est le plus lisible.
la source
gcc
ne le fera pas à coup sûr (il y a une bonne raison à cela). Clang optimisera les deux cas dans une recherche binaire. Par exemple, voyez ceci .Le commutateur, ne serait-ce que pour la lisibilité. Géant si les déclarations sont plus difficiles à maintenir et plus difficiles à lire à mon avis.
ERROR_01 : // échec intentionnel
ou
(ERROR_01 == numError) ||
Ce dernier est plus sujet aux erreurs et nécessite plus de frappe et de formatage que le premier.
la source
Code de lisibilité. Si vous voulez savoir ce qui fonctionne le mieux, utilisez un profileur, car les optimisations et les compilateurs varient et les problèmes de performances sont rarement là où les gens pensent qu'ils se trouvent.
la source
Utilisez switch, c'est ce à quoi il sert et ce que les programmeurs attendent.
Je mettrais les étiquettes de cas redondantes cependant - juste pour que les gens se sentent à l'aise, j'essayais de me rappeler quand / quelles sont les règles pour les laisser de côté.
Vous ne voulez pas que le prochain programmeur travaillant dessus ait à réfléchir inutilement aux détails de la langue (ce sera peut-être vous dans quelques mois!)
la source
Les compilateurs sont vraiment bons pour l'optimisation
switch
. Gcc récent est également bon pour optimiser un tas de conditions dans unif
.J'ai fait quelques cas de test sur godbolt .
Lorsque les
case
valeurs sont regroupées étroitement, gcc, clang et icc sont tous suffisamment intelligents pour utiliser une image bitmap pour vérifier si une valeur est l'une des valeurs spéciales.par exemple, gcc 5.2 -O3 compile le
switch
to (etif
quelque chose de très similaire):Notez que le bitmap est des données immédiates, donc il n'y a pas de manque potentiel de cache de données pour y accéder, ni de table de saut.
gcc 4.9.2 -O3 compile le
switch
en un bitmap, mais le fait1U<<errNumber
avec mov / shift. Il compile laif
version en série de branches.Notez comment il soustrait 1 de
errNumber
(aveclea
pour combiner cette opération avec un mouvement). Cela lui permet d'adapter le bitmap dans un 32 bits immédiat, évitant ainsi le 64 bits immédiatmovabsq
qui prend plus d'octets d'instructions.Une séquence plus courte (en code machine) serait:
(L'échec de l'utilisation
jc fire_special_event
est omniprésent et est un bogue du compilateur .)rep ret
est utilisé dans les cibles de branche, et après les branches conditionnelles, au profit des anciens AMD K8 et K10 (pré-Bulldozer): Que signifie «rep ret»? . Sans cela, la prédiction de branche ne fonctionne pas aussi bien sur ces processeurs obsolètes.bt
(bit test) avec un registre arg est rapide. Il combine le travail de décalage à gauche d'un 1 parerrNumber
bits et de faire untest
, mais c'est toujours une latence de 1 cycle et un seul uop Intel. C'est lent avec un argument mémoire en raison de sa sémantique trop CISC: avec un opérande mémoire pour la "chaîne de bits", l'adresse de l'octet à tester est calculée sur la base de l'autre arg (divisé par 8), et est n'est pas limité au bloc de 1, 2, 4 ou 8 octets pointé par l'opérande mémoire.D'après les tableaux d'instructions d'Agner Fog , une instruction de décalage à nombre variable est plus lente qu'une instruction
bt
Intel récente (2 uops au lieu de 1, et shift ne fait pas tout le reste).la source
Les avantages de switch () sur if else case sont: - 1. Switch est beaucoup plus efficace que if else car chaque cas ne dépend pas du cas précédent contrairement à if else où une déclaration individuelle doit être vérifiée pour une condition vraie ou fausse.
Quand il y a un non. des valeurs pour une seule expression, la casse de commutation est plus flexible que if else car dans if else, le jugement de cas est basé sur seulement deux valeurs, c'est-à-dire vrai ou faux.
Les valeurs dans switch sont définies par l'utilisateur, tandis que les valeurs dans if else case sont basées sur des contraintes.
En cas d'erreur, les instructions dans switch peuvent être facilement vérifiées et corrigées, ce qui est relativement difficile à vérifier en cas de déclaration if else.
Le boîtier de commutation est beaucoup plus compact et facile à lire et à comprendre.
la source
OMI, c'est un exemple parfait de la raison pour laquelle le basculement des commutateurs a été conçu.
la source
Si vos cas sont susceptibles de rester groupés à l'avenir - si plus d'un cas correspond à un résultat - le commutateur peut s'avérer plus facile à lire et à maintenir.
la source
Ils fonctionnent également bien. Les performances sont à peu près les mêmes avec un compilateur moderne.
Je préfère les instructions if aux instructions case car elles sont plus lisibles et plus flexibles - vous pouvez ajouter d'autres conditions non basées sur l'égalité numérique, comme "|| max <min". Mais pour le cas simple que vous avez publié ici, cela n'a pas vraiment d'importance, faites simplement ce qui vous est le plus lisible.
la source
le commutateur est définitivement préféré. Il est plus facile de consulter la liste des cas d'un commutateur et de savoir avec certitude ce qu'il fait que de lire la condition si longue.
La duplication de l'
if
état est dure pour les yeux. Supposons que l'un des a==
été écrit!=
; remarqueriez-vous? Ou si une instance de 'numError' a été écrite 'nmuError', qu'est-ce qui vient de se compiler?Je préfère généralement utiliser le polymorphisme au lieu du commutateur, mais sans plus de détails sur le contexte, c'est difficile à dire.
En ce qui concerne les performances, le mieux est d'utiliser un profileur pour mesurer les performances de votre application dans des conditions similaires à celles que vous attendez dans la nature. Sinon, vous optimisez probablement au mauvais endroit et dans le mauvais sens.
la source
Je suis d'accord avec la compacité de la solution de commutation, mais IMO vous détournez le commutateur ici.
Le but du commutateur est d'avoir une manipulation différente en fonction de la valeur.
Si vous deviez expliquer votre algo en pseudo-code, vous utiliseriez un if parce que, sémantiquement, c'est ce que c'est: si n'importe quelle
erreur fait ça ... Donc à moins que vous n'ayez l'intention un jour de changer votre code pour avoir un code spécifique pour chaque erreur , J'utiliserais si .
la source
Je choisirais l'énoncé if pour des raisons de clarté et de convention, même si je suis sûr que certains seraient en désaccord. Après tout, vous voulez faire quelque chose,
if
une condition est vraie! Avoir un interrupteur avec une action semble un peu ... inutile.la source
Je dirais utiliser SWITCH. De cette façon, vous n'avez qu'à mettre en œuvre des résultats différents. Vos dix cas identiques peuvent utiliser la valeur par défaut. Si un changement est tout ce dont vous avez besoin pour implémenter explicitement le changement, il n'est pas nécessaire de modifier la valeur par défaut. Il est également beaucoup plus facile d'ajouter ou de supprimer des cas d'un SWITCH que d'éditer IF et ELSEIF.
Peut-être même tester votre condition (dans ce cas numerror) par rapport à une liste de possibilités, un tableau peut-être pour que votre SWITCH ne soit même pas utilisé à moins qu'il y ait définitivement un résultat.
la source
Étant donné que vous n'avez que 30 codes d'erreur, codez votre propre table de saut, puis vous faites tous les choix d'optimisation vous-même (le saut sera toujours le plus rapide), plutôt que d'espérer que le compilateur fera la bonne chose. Cela rend également le code très petit (à part la déclaration statique de la table de saut). Il présente également l'avantage qu'avec un débogueur, vous pouvez modifier le comportement au moment de l'exécution si vous en avez besoin, simplement en poussant directement les données de la table.
la source
std::bitset<MAXERR> specialerror;
, alorsif (specialerror[err]) { special_handler(); }
. Ce sera plus rapide qu'une table de saut, en particulier. dans le cas non pris.Je ne suis pas sûr de la meilleure pratique, mais j'utiliserais le commutateur - puis j'intercepterais les échecs intentionnels via `` par défaut ''
la source
Esthétiquement, j'ai tendance à privilégier cette approche.
Rendez les données un peu plus intelligentes afin que nous puissions rendre la logique un peu plus stupide.
Je réalise que ça a l'air bizarre. Voici l'inspiration (de la façon dont je le ferais en Python):
la source
break
extérieur acase
(oui, un mineur péché, mais un péché néanmoins). :)La première est probablement optimisée par le compilateur, ce qui expliquerait pourquoi la deuxième boucle est plus lente lors de l'augmentation du nombre de boucles.
la source
if
rapportswitch
à une condition de fin de boucle en Java.Quand il s'agit de compiler le programme, je ne sais pas s'il y a une différence. Mais en ce qui concerne le programme lui-même et garder le code aussi simple que possible, je pense personnellement que cela dépend de ce que vous voulez faire. if else if else les déclarations ont leurs avantages, qui je pense sont:
vous permettent de tester une variable par rapport à des plages spécifiques, vous pouvez utiliser des fonctions (Standard Library ou Personal) comme conditionnelles.
(exemple:
Cependant, les instructions If else peuvent devenir compliquées et compliquées (malgré vos meilleures tentatives) à la hâte. Les instructions de commutation ont tendance à être plus claires, plus propres et plus faciles à lire; mais ne peut être utilisé que pour tester des valeurs spécifiques (exemple:
Je préfère les déclarations if - else if - else, mais cela dépend vraiment de vous. Si vous souhaitez utiliser des fonctions comme conditions, ou si vous souhaitez tester quelque chose par rapport à une plage, un tableau ou un vecteur et / ou que cela ne vous dérange pas de gérer l'imbrication compliquée, je vous recommande d'utiliser If else if else blocs. Si vous voulez tester des valeurs uniques ou si vous voulez un bloc propre et facile à lire, je vous recommande d'utiliser les blocs de cas switch ().
la source
Je ne suis pas la personne pour vous parler de la vitesse et de l'utilisation de la mémoire, mais regarder une déclaration de commutateur est beaucoup plus facile à comprendre qu'une grande déclaration if (en particulier 2-3 mois plus tard)
la source
Je sais que c'est vieux mais
Varier le nombre de boucles change beaucoup:
Tandis que if / else: Commutateur 5ms: 1ms Boucles max: 100000
Tandis que if / else: Commutateur 5ms: 3ms Boucles max: 1000000
Tandis que if / else: Commutateur 5ms: 14ms Boucles maximum: 10000000
Tandis que if / else: Commutateur 5ms: 149ms Boucles maximum: 100000000
(ajoutez plus de déclarations si vous le souhaitez)
la source
if(max) break
boucle s'exécute en temps constant quel que soit le nombre de boucles? On dirait que le compilateur JIT est suffisamment intelligent pour optimiser la bouclecounter2=max
. Et peut-être que c'est plus lent que de changer si le premier appel àcurrentTimeMillis
a plus de surcharge, parce que tout n'est pas encore compilé JIT? Mettre les boucles dans l'autre ordre donnerait probablement des résultats différents.