[java] 스위치가 더 빠른 이유

많은 Java 책에서이 switch명령문이 if else명령문 보다 빠르다고 설명합니다 . 그러나 나는 왜 스위치가 더 빠른지 어디서도 찾지 못했습니다 .

두 가지 중 하나를 선택해야하는 상황이 있습니다. 어느 쪽이든 사용할 수 있습니다

switch (item) {
    case BREAD:
        //eat Bread
        break;
    default:
        //leave the restaurant
}

또는

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

항목과 BREAD를 고려하면 상수 int 값입니다.

위의 예 에서 더 빠른 행동과 그 이유는 무엇입니까?



답변

많은 경우가있을 때 효율적인 switch 문 평가를 허용하는 특수 바이트 코드가 있기 때문입니다.

IF 문으로 구현 된 경우 검사, 다음 절로의 점프, 다음 절로의 점프 등이 있습니다. 스위치를 사용하면 JVM이 비교할 값을로드하고 값 테이블을 반복하여 일치 항목을 찾습니다. 이는 대부분의 경우 더 빠릅니다.


답변

switch문은 빠를 것보다 항상없는 if문. 모든 값을 기반으로 조회를 수행 할 수 있으므로 긴 if-else문 목록보다 확장 성이 좋습니다 switch. 그러나 짧은 조건의 경우 더 빠르지 않고 더 느릴 수 있습니다.


답변

현재 JVM에는 LookupSwitch와 TableSwitch라는 두 가지 종류의 스위치 바이트 코드가 있습니다.

switch 문의 각 case에는 정수 오프셋이 있습니다. 이러한 오프셋이 연속적 (또는 큰 간격없이 대부분 연속적) 인 경우 (case 0 : case 1 : case 2 등) TableSwitch가 사용됩니다.

오프셋이 큰 간격 (case 0 : case 400 : case 93748 : 등)으로 분산 된 경우 LookupSwitch가 사용됩니다.

간단히 말해서, 가능한 값 범위 내의 각 값에 특정 바이트 코드 오프셋이 주어지기 때문에 TableSwitch가 일정한 시간에 수행된다는 점이 다릅니다. 따라서 명령문에 3의 오프셋을 제공하면 올바른 분기를 찾기 위해 3으로 건너 뛰는 것을 알고 있습니다.

조회 스위치는 이진 검색을 사용하여 올바른 코드 분기를 찾습니다. 이것은 O (log n) 시간에 실행되며 여전히 좋지만 최고는 아닙니다.

이에 대한 자세한 내용은 여기를 참조하십시오. JVM의 LookupSwitch와 TableSwitch의 차이점은 무엇입니까?

어느 것이 가장 빠른지에 관해서는 다음 접근 방식을 사용하십시오. 값이 연속적이거나 거의 연속적인 케이스가 3 개 이상있는 경우 항상 스위치를 사용하십시오.

케이스가 2 개인 경우 if 문을 사용하십시오.

다른 상황의 경우, 스위치는 대부분 더 빠를 LookupSwitch의 이진 검색이 나쁜 시나리오에 맞을 수 있기 때문에 보장되지 않습니다.

또한 JVM은 코드에서 가장 인기있는 분기를 먼저 배치하려고 시도하는 if 문에서 JIT 최적화를 실행합니다. 이를 “지점 예측”이라고합니다. 이에 대한 자세한 내용은 https://dzone.com/articles/branch-prediction-in-java를 참조하십시오.

귀하의 경험은 다를 수 있습니다. JVM이 LookupSwitch에서 유사한 최적화를 실행하지 않는다는 것은 모르지만 JIT 최적화를 신뢰하고 컴파일러를 능가하려고하지 않는 법을 배웠습니다.


답변

따라서 패킷로드를 계획하는 경우 요즘 메모리는 실제로 큰 비용이 아니며 어레이는 매우 빠릅니다. 또한 switch 문을 사용하여 점프 테이블을 자동으로 생성 할 수 없으므로 점프 테이블 시나리오를 직접 생성하는 것이 더 쉽습니다. 아래 예에서 볼 수 있듯이 최대 255 개의 패킷을 가정합니다.

아래의 결과를 얻으려면 추상화가 필요합니다 .. 어떻게 작동하는지 설명하지 않을 것입니다.

(id <0)에 대한 경계 검사를 수행해야하는 것보다 더 필요한 경우 패킷 크기를 255로 설정하도록 업데이트했습니다. (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 ++에서 점프 테이블을 많이 사용하므로 편집 이제 함수 포인터 점프 테이블의 예를 보여 드리겠습니다. 이것은 매우 일반적인 예이지만 실행했으며 올바르게 작동합니다. 포인터를 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 문을 더 이상 줄일 수 없습니다.

즉, 지저분하고 빠르게 관리하기가 어렵고 다른 접근 방식이 일반적으로 더 좋지만 어레이가 그것을 자르지 않는 경우에 활용됩니다. 사용 사례와 각 상황이 가장 잘 작동하는시기를 파악해야합니다. 몇 가지 검사 만있는 경우 이러한 접근 방식 중 하나를 사용하지 않으려는 것처럼.

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 {

                              }
                          }
                      }
                  }
             }
        }
   }
}


답변

바이트 코드 수준에서 주제 변수는 런타임에 의해로드 된 구조화 된 .class 파일의 메모리 주소에서 프로세서 레지스터로 한 번만로드되며 이는 switch 문에 있습니다. 반면 if 문에서는 코드 컴파일 DE에 의해 다른 jvm 명령어가 생성되며, 이는 다음 선행 if 문에서와 동일한 변수가 사용 되더라도 각 변수를 레지스터에로드해야합니다. 어셈블리 언어로 코딩하는 것을 안다면 이것은 흔한 일입니다. 자바로 컴파일 된 cox는 바이트 코드 나 직접적인 기계 코드가 아니지만, 여기의 조건부 개념은 여전히 ​​일관됩니다. 글쎄, 나는 설명 할 때 더 깊은 전문성을 피하려고 노력했다. 나는 개념을 명확하고 이해하기 쉽게 만들었기를 바랍니다. 감사합니다.


답변