스위치가 더 빠른 이유


116

많은 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 값입니다.

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


어쩌면이 대답은 자바에 대한 또한 : stackoverflow.com/questions/767821/...
토비아스

19
일반적으로 Wikipedia에서 : 입력 값의 범위가 식별 가능하게 '작고'몇 개의 간격 만있는 경우 최적화 프로그램을 통합하는 일부 컴파일러는 실제로 switch 문을 분기 테이블 또는 인덱스 함수 포인터 배열로 구현할 수 있습니다. 긴 일련의 조건부 명령. 이를 통해 switch 문은 비교 목록을 거치지 않고도 실행할 분기를 즉시 결정할 수 있습니다.
Felix Kling 2011

질문에 대한 최고의 답변은 그것을 잘 설명합니다. 이 기사 는 모든 것을 아주 잘 설명합니다.
bezmax

대부분의 상황에서 최적화 컴파일러가 유사한 성능 특성을 가진 코드를 생성 할 수 있기를 바랍니다. 어쨌든 차이를 알아 차리려면 수백만 번 전화해야합니다.
Mitch Wheat

2
설명 / 증거 / 추론없이 이와 같은 진술을하는 책에주의해야합니다.
matt b

답변:


110

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

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


6
반복은 "확인, 점프"로 번역되지 않습니까?
fivetwentysix 2011

17
@fivetwentysix : 아니요, 정보는 artima.com/underthehood/flowP.html 을 참조 하세요 . 기사 인용 : JVM이 테이블 스위치 명령을 만나면 키가 낮음과 높음으로 정의 된 범위 내에 있는지 간단히 확인할 수 있습니다. 그렇지 않은 경우 기본 분기 오프셋을 사용합니다. 그렇다면 분기 오프셋 목록에 오프셋을 가져 오기 위해 키에서 낮은 값을 뺍니다. 이러한 방식으로 각 케이스 값을 확인하지 않고도 적절한 분기 오프셋을 결정할 수 있습니다.
bezmax 2011-07-15

1
(i) a switchtableswitch바이트 코드 명령어 로 변환되지 않을 수 있습니다. - lookupswitchif / else와 유사하게 수행되는 tableswitch명령어가 될 수 있습니다. (ii) 바이트 코드 명령어 조차도 요인에 따라 JIT에 의해 일련의 if / else로 컴파일 될 수 있습니다. cases 의 수와 같은 .
assylias


34

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


5
"long"을 제한하십시오. 5보다 큽니까? 10보다 큽니까? 아니면 20-30 정도?
vanderwyst

11
상황에 따라 다릅니다. 나에게는 3 개 이상의 제안 switch이 더 빠르지 않으면 더 명확 할 것입니다.
Peter Lawrey

어떤 조건에서 더 느릴 수 있습니까?
Eric

1
@Eric 적은 수의 값 esp String 또는 int가 드문 경우 느립니다.
Peter Lawrey

8

현재 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 최적화를 신뢰하고 컴파일러를 능가하려고하지 않는 법을 배웠습니다.


1
이 글을 게시 한 이후로 "스위치 표현식"과 "패턴 매칭"이 Java 12에 곧바로 나올 것입니다 . openjdk.java.net/jeps/325 openjdk.java.net/jeps/305 아직 구체적인 것은 없지만 이것이 switch훨씬 더 강력한 언어 기능을 만들 것으로 보입니다 . 예를 들어, 패턴 일치는 훨씬 더 부드럽고 성능이 좋은 instanceof조회를 허용 합니다. 그러나 기본 스위치 / if 시나리오의 경우 내가 언급 한 규칙이 여전히 적용된다고 가정하는 것이 안전하다고 생각합니다.
HesNotTheStig

1

따라서 패킷로드를 계획하는 경우 요즘 메모리는 실제로 큰 비용이 아니며 어레이는 매우 빠릅니다. 또한 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 {

                              }
                          }
                      }
                  }
             }      
        }
   }
}

2
이중 간접적 인 이유는 무엇입니까? ID 어쨌든 제한되어야하므로, 왜 그냥 같은 수신 ID를 경계 체크 0 <= id < packets.length하고 확인 packets[id]!=null하고 할 packets[id].execute(data)?
Lawrence Dol 2014

그래, 늦게 응답해서 미안해. 다시 봤는데 내가 무슨 생각을했는지 모르겠다. 내가 포스트 lol을 업데이트하고 패킷을 서명되지 않은 바이트 크기로 제한했기 때문에 길이 확인이 필요하지 않다.
Jeremy Trifilo

0

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.