Почему переключаться быстрее, чем если - PullRequest
100 голосов
/ 15 июля 2011

Я нашел много книг в java, в которых говорится, что оператор switch работает быстрее, чем оператор else.Но я не нашел в другом месте высказывание почему переключение происходит быстрее, чем если бы .

Пример

У меня есть ситуация, я должен выбрать любой элемент из двух, которые я могу использовать какследующего способа

switch(item){

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

}

или с помощью оператора if, подобного следующему

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

с учетом элемента, а BREAD - это постоянное значение типа int

Inприведенный выше пример, который быстрее в действии и почему?

Ответы [ 5 ]

97 голосов
/ 15 июля 2011

Потому что существуют специальные байт-коды, которые позволяют эффективно оценивать операторы переключения при большом количестве случаев.

Если реализовано с помощью IF-операторов, у вас будет проверка, переход к следующему предложению, проверка, переход к следующему предложению и так далее. С помощью переключателя JVM загружает значение для сравнения и перебирает таблицу значений, чтобы найти совпадение, что в большинстве случаев быстрее.

31 голосов
/ 15 июля 2011

A switch оператор не всегда быстрее, чем if оператор. Он масштабируется лучше, чем длинный список if-else операторов, поскольку switch может выполнять поиск на основе всех значений. Однако для коротких условий это не будет быстрее и может быть медленнее.

7 голосов
/ 04 января 2018

Текущая JVM имеет два вида байтовых кодов переключения: LookupSwitch и TableSwitch.

Каждый случай в выражении switch имеет целочисленное смещение, если эти смещения являются смежными (или в основном смежными без больших пробелов) (случай 0: случай 1: случай 2 и т. д.), то используется TableSwitch.

Если смещения разбиты с большими промежутками (случай 0: случай 400: случай 93748: и т. д.), то LookupSwitch

Короче говоря, разница в том, что TableSwitch выполняется за постоянное время, потому что каждому значению в диапазоне возможных значений присваивается конкретное смещение байтового кода.Таким образом, когда вы задаете для оператора смещение 3, он знает, что нужно перейти на 3, чтобы найти правильную ветвь.

Переключатель поиска использует двоичный поиск, чтобы найти правильную ветвь кода.Это выполняется за O (log n), что по-прежнему хорошо, но не лучше.

Подробнее об этом см. Здесь: Разница между LookupSwitch JVM и TableSwitch?

Чтобы определить, какой из них самый быстрый, используйте этот подход: если у вас есть 3 или более случаев, значения которых являются последовательными или почти последовательными, всегда используйте переключатель.

Если у вас есть 2 случая, используйтеоператор if.

В любой другой ситуации переключатель на , скорее всего, быстрее, но это не гарантировано, поскольку бинарный поиск в LookupSwitch может привести к неудачному сценарию.

Также имейте в виду, что JVM будет запускать JIT-оптимизацию для операторов if, которые будут пытаться поместить самую горячую ветвь первой в коде.Это называется "прогнозирование ветвления".Подробнее об этом см. Здесь: https://dzone.com/articles/branch-prediction-in-java

Ваш опыт может отличаться.Я не знаю, что JVM не выполняет подобную оптимизацию на LookupSwitch, но я научился доверять оптимизациям JIT и не пытаться перехитрить компилятор.

1 голос
/ 02 ноября 2012

Так что, если вы планируете загружать пакеты, память в наши дни не такая уж и большая, а массивы довольно быстрые.Вы также не можете полагаться на оператор switch для автоматической генерации таблицы переходов, поэтому проще создать сценарий таблицы переходов самостоятельно.Как видно из приведенного ниже примера, мы принимаем максимум 255 пакетов.

Чтобы получить приведенный ниже результат, вам нужна абстракция. Я не собираюсь объяснять, как это работает, надеюсь, вы понимаете это.

Я обновил это, чтобы установить размер пакета равным255 если вам нужно больше, вам нужно будет выполнить проверку границ для (id <0) ||(id> length).

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

Правка, поскольку я часто использую таблицу переходов в C ++, сейчас я покажу пример таблицы переходов указателя функции.Это очень общий пример, но я запустил его, и он работает правильно.Помните, что вы должны установить указатель на NULL, C ++ не будет делать это автоматически, как в 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;
}

Также еще один момент, который я хотел бы затронуть, это знаменитый Divide and Conquer.Таким образом, моя вышеупомянутая идея массива 255 может быть уменьшена до не более 8, если операторы являются худшим сценарием.

Т.е., но имейте в виду, что это становится беспорядочным и трудно управляемым, а мой другой подход обычно лучше, ноэто используется в тех случаях, когда массивы просто не обрезают его.Вы должны выяснить свой вариант использования и когда каждая ситуация работает лучше всего.Точно так же, как вы не захотите использовать ни один из этих подходов, если у вас есть только несколько проверок.

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 {

                              }
                          }
                      }
                  }
             }      
        }
   }
}
0 голосов
/ 14 февраля 2014

На уровне байт-кода переменная субъекта загружается только один раз в регистр процессора с адреса памяти в структурированном файле .class, загруженном средой выполнения, и это находится в операторе switch;в то время как в операторе if ваша DE-компилятор кода создает другую инструкцию jvm, и для этого требуется, чтобы каждая переменная загружалась в регистры, хотя используется та же переменная, что и в следующем предыдущем операторе if.Если вы знаете кодирование на ассемблере, то это будет обычным делом;хотя скомпилированные Java-коды не являются байт-кодом или прямым машинным кодом, условная концепция по-прежнему остается неизменной.Ну, я попытался избежать более глубоких технических соображений при объяснении.Я надеюсь, что я сделал концепцию ясной и демистифицированной.Спасибо.

...