web-dev-qa-db-fra.com

Pourquoi changer est plus rapide que si

J'ai trouvé beaucoup de livres dans Java) dire que l'instruction switch est plus rapide que si l'instruction else. Mais je n'ai pas trouvé antwhere disant pourquoi la commutation est plus rapide que si.

Exemple

J'ai une situation où je dois choisir un article sur deux que je peux utiliser de la manière suivante

switch(item){

case BREAD:
     //eat Bread
break;
default:
    //leave the restaurant

}

ou en utilisant si déclaration comme la suivante

if(item== BREAD){
//eat Bread
}else{
//leave the restaurant
}

compte tenu de l'élément et BREAD est une valeur int constante

Dans l'exemple ci-dessus qui est plus rapide en action et pourquoi?

104
user831722

Parce qu'il existe des bytecodes spéciaux qui permettent une évaluation efficace des instructions de commutateur dans de nombreux cas.

Si implémenté avec des instructions IF, vous auriez une vérification, un saut à la clause suivante, une vérification, un saut à la clause suivante et ainsi de suite. Avec switch, la machine virtuelle Java charge la valeur à comparer et effectue une itération dans la table des valeurs pour trouver une correspondance, ce qui est plus rapide dans la plupart des cas.

100
Daniel

Une instruction switch n'est pas toujours plus rapide qu'une instruction if. Il évolue mieux qu'une longue liste de if-else Les instructions comme switch peuvent effectuer une recherche basée sur toutes les valeurs. Cependant, dans le cas d'une condition brève, ce ne sera pas plus rapide et pourrait être plus lent.

31
Peter Lawrey

La JVM actuelle comporte deux types de codes d'octet de commutateur: LookupSwitch et TableSwitch.

Chaque cas dans une instruction switch a un décalage entier, si ces décalages sont contigus (ou généralement contigus sans grands espaces) (cas 0: cas 1: cas 2, etc.), alors TableSwitch est utilisé.

Si les décalages sont répartis avec de grands espaces (cas 0: cas 400: cas 93748:, etc.), alors LookupSwitch est utilisé.

En bref, la différence est que TableSwitch est effectué en temps constant car chaque valeur comprise dans la plage de valeurs possibles reçoit un décalage de code octet spécifique. Ainsi, lorsque vous attribuez à la déclaration un décalage de 3, il sait passer à 3 pour trouver la branche correcte.

Le commutateur de recherche utilise une recherche binaire pour trouver la bonne branche de code. Cela fonctionne en temps O (log n), ce qui est toujours bon, mais pas le meilleur.

Pour plus d'informations à ce sujet, voir ici: Différence entre LookupSwitch et TableSwitch?

Pour déterminer lequel est le plus rapide, utilisez cette approche: si vous avez 3 cas ou plus dont les valeurs sont consécutives ou presque consécutives, utilisez toujours un commutateur.

Si vous avez 2 cas, utilisez une instruction if.

Pour toute autre situation, switch est très probablement plus rapide, mais ce n'est pas garanti, car la recherche binaire dans LookupSwitch pourrait se produire dans un mauvais scénario.

De plus, n'oubliez pas que la machine virtuelle Java exécutera les optimisations JIT sur les instructions if qui essaieront de placer la branche la plus à jour en premier dans le code. Cela s'appelle "Prédiction de branche". Pour plus d'informations à ce sujet, voir ici: https://dzone.com/articles/branch-prediction-in-Java

Vos expériences peuvent varier. Je ne sais pas si la machine virtuelle Java n'exécute pas une optimisation similaire sur LookupSwitch, mais j'ai appris à faire confiance aux optimisations JIT et à ne pas essayer de déjouer le compilateur.

7
HesNotTheStig

Donc, si vous envisagez d'avoir des charges de paquets de mémoire, le coût n'est pas élevé et les baies sont assez rapides. Vous ne pouvez pas non plus vous fier à une instruction switch pour générer automatiquement une table de saut. Il est donc plus facile de générer le scénario de table de saut vous-même. Comme vous pouvez le voir dans l'exemple ci-dessous, nous supposons un maximum de 255 paquets.

Pour obtenir le résultat ci-dessous, votre besoin d'abstraction. Je ne vais pas expliquer comment cela fonctionne. J'espère que vous l'aurez compris.

J'ai mis à jour ceci pour définir la taille du paquet à 255 si vous avez besoin de plus que vous devrez faire une vérification des limites pour (id <0) || (id> longueur).

Packets[] packets = new Packets[255];

static {
     packets[0] = new Login(6);
     packets[2] = new Logout(8);
     packets[4] = new GetMessage(1);
     packets[8] = new AddFriend(0);
     packets[11] = new JoinGroupChat(7); // etc... not going to finish.
}

public void handlePacket(IncomingData data)
{
    int id = data.readByte() & 0xFF; //Secure value to 0-255.

    if (packet[id] == null)
        return; //Leave if packet is unhandled.

    packets[id].execute(data);
}

Modifier depuis que j'utilise beaucoup une table de saut en C++, je vais maintenant montrer un exemple de table de saut de pointeur de fonction. Ceci est un exemple très générique, mais je l'ai exécuté et cela fonctionne correctement. Gardez à l'esprit que vous devez définir le pointeur sur NULL, C++ ne le fera pas automatiquement comme en Java.

#include <iostream>

struct Packet
{
    void(*execute)() = NULL;
};

Packet incoming_packet[255];
uint8_t test_value = 0;

void A() 
{ 
    std::cout << "I'm the 1st test.\n";
}

void B() 
{ 
    std::cout << "I'm the 2nd test.\n";
}

void Empty() 
{ 

}

void Update()
{
    if (incoming_packet[test_value].execute == NULL)
        return;

    incoming_packet[test_value].execute();
}

void InitializePackets()
{
    incoming_packet[0].execute = A;
    incoming_packet[2].execute = B;
    incoming_packet[6].execute = A;
    incoming_packet[9].execute = Empty;
}

int main()
{
    InitializePackets();

    for (int i = 0; i < 512; ++i)
    {
        Update();
        ++test_value;
    }
    system("pause");
    return 0;
}

Un autre point que je voudrais aborder est le fameux Divide and Conquer. Donc, mon idée de tableau ci-dessus 255 pourrait être réduite à pas plus de 8 si if déclarations comme le pire des cas.

C'est à dire. mais gardez à l'esprit que cela devient désordonné et difficile à gérer rapidement et que mon autre approche est généralement meilleure, mais elle est utilisée dans les cas où les tableaux ne suffiront tout simplement pas. Vous devez déterminer votre cas d'utilisation et le moment où chaque situation fonctionne le mieux. Tout comme vous ne voudriez utiliser aucune de ces approches si vous n'avez que quelques vérifications.

If (Value >= 128)
{
   if (Value >= 192)
   {
        if (Value >= 224)
        {
             if (Value >= 240)
             {
                  if (Value >= 248)
                  {
                      if (Value >= 252)
                      {
                          if (Value >= 254)
                          {
                              if (value == 255)
                              {

                              } else {

                              }
                          }
                      }
                  }
             }      
        }
   }
}
1
Jeremy Trifilo

Au niveau du bytecode, la variable sujet n'est chargée qu'une seule fois dans le registre du processeur à partir d'une adresse de mémoire du fichier .class structuré chargé par Runtime, et cela se trouve dans une instruction switch. alors que dans une instruction if, une instruction jvm différente est produite par votre DE compilateur de code, ce qui nécessite que chaque variable soit chargée dans des registres bien que la même variable soit utilisée comme dans l'instruction if précédente précédente. Si vous connaissez le codage en langage d'assemblage, cela serait courant; Bien que Java les barrages compilés ne sont pas du code machine, ni du code machine direct, le concept conditionnel reste cohérent. Bien, j’ai essayé d’éviter une technique plus approfondie en expliquant. J'espère avoir clairement expliqué le concept et démystifié, merci.

0
Verse Villalon Gamboa