Почему переключатель быстрее, чем если

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

Пример

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

switch(item){

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

}

или используя оператор if, как показано ниже

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

рассматриваемый элемент и BREAD - постоянное значение int

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

Ответ 1

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

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

Ответ 2

Оператор

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

Ответ 3

Текущая 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 и не пытаться перехитрить компилятор.

Ответ 4

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

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

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

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

Packet incoming_packet[255];

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

void A() { }
void B() { }
void C() { }
void D() { }
void DoNothing() { }

void InitializePackets()
{
     incoming_packet[0] = A;
     incoming_packet[2] = B;
     incoming_packet[6] = C;
     incoming_packet[9] = D;
}


void ProcessPacket(IncomingData &data)
{
   uint8_t id = data.ReadByte();
   if (incoming_packet[id] == NULL)
       return;
   incoming_packet[id].execute();
}

Ответ 5

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