web-dev-qa-db-fra.com

Si vs vitesse de commutation

Les instructions switch sont généralement plus rapides que les instructions if-else-if équivalentes (comme par exemple, décrites dans cet article article ) en raison des optimisations du compilateur.

Comment fonctionne cette optimisation? Est-ce que quelqu'un a une bonne explication?

106
Dirk Vollmar

Le compilateur peut créer des tables de saut, le cas échéant. Par exemple, lorsque vous utilisez le réflecteur pour examiner le code produit, vous verrez que pour les commutateurs énormes sur les chaînes, le compilateur génère un code qui utilise une table de hachage pour les envoyer. La table de hachage utilise les chaînes comme clés et les délégués les codes case comme valeurs.

Cela a un meilleur temps d'exécution asymptotique que beaucoup de tests if enchaînés et est en fait plus rapide, même pour relativement peu de chaînes.

174
Konrad Rudolph

Konrad est correct. Dans le cas de l'activation de plages entières contiguës (par exemple, si vous avez le cas 0, le cas 1, le cas 2 ... le cas n), le compilateur peut faire encore mieux car il n'a même pas besoin de construire une table de hachage; il stocke simplement un tableau de pointeurs de fonction et peut donc charger sa cible de saut en temps constant.

29
Crashworks

Ceci est une légère simplification, car généralement tout compilateur moderne rencontrant une séquence if..else if .. pouvant être convertie de manière triviale en une instruction switch par une personne, le compilateur le sera également. Mais juste pour ajouter du plaisir, le compilateur n’est pas limité par la syntaxe, il peut donc générer des instructions "switch" similaires à celles internes, combinant des plages, des cibles uniques, etc. .else déclarations.

Anyhoo, une extension de la réponse de Konrad est que le compilateur peut générer une table de sauts, mais ce n'est pas nécessairement garanti (ni souhaitable). Pour diverses raisons, les tables de saut affectent mal les prédicteurs de branche des processeurs modernes, et les tables elles-mêmes affectent mal le comportement en cache, par exemple.

switch(a) { case 0: ...; break; case 1: ...; break; }

Si un compilateur générait réellement une table de saut pour cela, il serait probablement plus lent que le code de style alternatif if..else if.. en raison du fait que la table de saut annulait la prédiction de branche.

13
olliej

Les instructions switch/case peuvent être généralement plus rapides au niveau 1, mais lorsque vous commencez à entrer dans 2 ou plus, les instructions switch/case prennent 2-3 fois plus longtemps que les instructions if/else imbriquées.

Cet article présente des comparaisons de vitesse soulignant les différences de vitesse lorsque de telles instructions sont imbriquées.

Par exemple, selon leurs tests, des exemples de code ressemblent à ce qui suit:

if (x % 3 == 0)
            if (y % 3 == 0)
                total += 3;
            else if (y % 3 == 1)
                total += 2;
            else if (y % 3 == 2)
                total += 1;
            else
                total += 0;
        else if (x % 3 == 1)
            if (y % 3 == 0)
                total += 3;
            else if (y % 3 == 1)
                total += 2;
            else if (y % 3 == 2)
                total += 1;
            else
                total += 0;
        else if (x % 3 == 2)
            if (y % 3 == 0)
                total += 3;
            else if (y % 3 == 1)
                total += 2;
            else if (y % 3 == 2)
                total += 1;
            else
                total += 0;
        else
            if (y % 3 == 0)
                total += 3;
            else if (y % 3 == 1)
                total += 2;
            else if (y % 3 == 2)
                total += 1;
            else
                total += 0;

terminé dans demi le temps nécessaire à l'exécution de l'instruction switch/case équivalente:

switch (x % 3)
    {
        case 0:
            switch (y % 3)
            {
                case 0: total += 3;
                    break;
                case 1: total += 2;
                    break;
                case 2: total += 1;
                    break;
                default: total += 0;
                    break;
            }
            break;
        case 1:
            switch (y % 3)
            {
                case 0: total += 3;
                    break;
                case 1: total += 2;
                    break;
                case 2: total += 1;
                    break;
                default: total += 0;
                    break;
            }
            break;
    case 2:
            switch (y % 3)
            {
                case 0: total += 3;
                    break;
                case 1: total += 2;
                    break;
                case 2: total += 1;
                    break;
                default: total += 0;
                    break;
            }
            break;
    default:
        switch (y % 3)
        {
            case 0: total += 3;
                break;
            case 1: total += 2;
                break;
            case 2: total += 1;
                break;
            default: total += 0;
                break;
        }
        break;
    }

Oui, c'est un exemple rudimentaire, mais cela illustre bien le propos. 

Donc, une conclusion pourrait être utiliser switch/case pour les types simples qui ont un seul niveau de profondeur, mais pour des comparaisons plus complexes et plusieurs niveaux imbriqués, utilisez les constructions classiques if/else?

5
Thrawn Wannabe

Comme le disait Konrad, le compilateur peut construire une table de saut. 

En C++, cela peut être dû à la limitation des commutateurs. 

5
J.J.

Les statistiques sans correspondance peuvent ne pas être bonnes.

Si vous téléchargez réellement la source, les valeurs sans correspondance sont 21, dans les cas si et le cas. Un compilateur doit être en mesure d’abstraire, sachant quelle instruction doit être exécutée à tout moment, et un processeur doit pouvoir effectuer une branche correctement.

Le cas le plus intéressant est celui où, à mon avis, tous les cas ne se cassent pas, mais que cela n’était peut-être pas la portée de l’expérience. 

4
Calyth

Le seul avantage du cas «over over» est qu'il existe une augmentation notable de la fréquence d'occurrence du premier cas. 

Je ne sais pas exactement où se trouve le seuil, mais j'utilise la syntaxe de casse sauf si le premier "presque toujours" passe le premier test. 

0
Ralph

C’est le code du microcontrôleur PIC18 en langage C:

void main() {
int s1='0';
int d0;
int d1;
//if (s1 == '0') {d1 = '0'; d0 = '0';}
//else if (s1 == '1') {d1 = '0';d0 = '1';}
//else if (s1 == '2') {d1 = '1';d0 = '0';}
//else if (s1 == '3') {d1 = '1';d0 = '1';}
switch (s1) {
      case  '0': {d1 = '0';d0 = '0';} break;
      case  '1': {d1 = '0';d0 = '1';} break;
      case  '2': {d1 = '1';d0 = '0';} break;
      case  '3': {d1 = '1';d0 = '1';} break;
    }
}

Avec ifs

s1='0' - 14 cycles
s1='1' - 21 cycles
s1='2' - 28 cycles
s1='3' - 33 cycles
s1='4' - 34 cycles

Avec étuis

s1='0' - 17 cycles
s2='1' - 23 cycles
s3='2' - 29 cycles
s4='3' - 35 cycles
s5='4' - 32 cycles

Je peux donc supposer que si très bas niveau if est plus rapide. Le code dans la ROM est également plus court.

0
Denys Titarenko