If 대 스위치 속도


112

Switch 문은 일반적으로 컴파일러 최적화로 인해 동등한 if-else-if 문 (예 :이 기사 에서 설명 ) 보다 빠릅니다 .

이 최적화는 실제로 어떻게 작동합니까? 누구에게 좋은 설명이 있습니까?



가능한 좋은 답변 : dotnetperls.com/if-switch-performance
바박

답변:


185

컴파일러는 해당되는 경우 점프 테이블을 빌드 할 수 있습니다. 예를 들어 리플렉터를 사용하여 생성 된 코드를 살펴보면 문자열의 큰 스위치에 대해 컴파일러가 실제로 해시 테이블을 사용하여이를 디스패치하는 코드를 생성한다는 것을 알 수 있습니다. 해시 테이블은 문자열을 키로 사용하고 case코드를 값으로 위임 합니다.

이것은 많은 체인 if테스트 보다 점근 적으로 더 나은 런타임을 가지며 실제로 상대적으로 적은 문자열에서도 더 빠릅니다.


6
해시 테이블에 대한 좋은 대답입니다.
BobbyShaftoe

4
또한 경우에 따라 트리 비교로 변환됩니다. 추론은 다소 복잡하지만 기본적으로 테이블 간접적 인 현대 CPU 점프 대상 버퍼를 중성화하여 분기 예측기를 제거합니다. 스위치 용 코드 젠에 관한 GCC 컨퍼런스의 논문을 막연하게 회상합니다.
olliej

즉, 스위치 (a) case "x": case "y": case "z": // 무언가가 깨졌습니다. }보다 빠릅니다 : if (a == "x"|| a == "b"|| a == "c") // 뭔가 맞습니까?
yazanpro

여기서 우리는 중첩되지 않고 OR 만 있으므로 어떻게 생각하십니까?
yazanpro 2012

@yazanpro 오래된 컴파일러에서는 잠재적으로 가능합니다 (그러나 케이스 수가 너무 적어서 차이를 만들 수 없습니다!). 현대 컴파일러는 훨씬 더 많은 코드 분석을 수행합니다. 결과적으로 두 코드 스 니펫이 동일하다는 것을 알아 내고 동일한 최적화를 적용 할 수 있습니다. 그러나 이것은 내 부분에 대한 순수한 추측이며 컴파일러가 실제로 그렇게하는지 여부는 알 수 없습니다.
Konrad Rudolph

15

이것은 if..else if ..사람에 의해 사소하게 switch 문으로 변환 될 수 있는 시퀀스 를 만나는 현대 컴파일러처럼 약간 단순화 된 것입니다. 컴파일러도 마찬가지입니다. 그러나 재미를 더하기 위해 컴파일러는 구문에 의해 제한되지 않으므로 범위, 단일 대상 등이 혼합 된 내부적으로 "switch"와 같은 명령문을 생성 할 수 있으며 switch와 if 모두에 대해이 작업을 수행 할 수 있습니다. .else 문.

Anyhoo, Konrad의 답변에 대한 확장은 컴파일러가 점프 테이블을 생성 할 수 있지만 반드시 보장되는 것은 아닙니다 (바람직하지 않음). 다양한 이유로 점프 테이블은 최신 프로세서의 분기 예측 자에 나쁜 일을하고 테이블 자체는 행동을 캐시하기 위해 나쁜 일을합니다.

switch(a) { case 0: ...; break; case 1: ...; break; }

컴파일러가 실제로 이에 대한 점프 테이블을 생성했다면 점프 테이블이 if..else if..분기 예측을 무너 뜨리기 때문에 대체 스타일 코드 보다 느릴 수 있습니다 .


4

불일치 통계는 좋지 않을 수 있습니다.

실제로 소스를 다운로드하는 경우 일치하지 않는 값은 if 및 switch 케이스 모두에서 21로 알려져 있습니다. 컴파일러는 어떤 문이 항상 실행되어야하는지 알고 추상화 할 수 있어야하며 CPU는 올바르게 분기 예측할 수 있어야합니다.

더 흥미로운 경우는 내 생각에 모든 사례가 깨지는 것은 아니지만 그것이 실험의 범위가 아닐 수도 있다는 것입니다.


4

Switch / case 문은 일반적으로 1 단계 깊이에서 더 빠를 수 있지만 2 개 이상을 시작하면 switch / case 문이 중첩 된 if / else 문보다 2-3 배 오래 걸리기 시작합니다.

이 기사에는 이러한 문이 중첩 될 때 속도 차이를 강조하는 몇 가지 속도 비교 가 있습니다.

예를 들어 테스트에 따르면 다음과 같은 샘플 코드가 있습니다.

if (x % 3 == 0)
            if (y % 3 == 0)
                total += 3;
            else if (y % 3 == 1)
                total += 2;
            else if (y % 3 == 2)
                total += 1;
            else
                total += 0;
        else if (x % 3 == 1)
            if (y % 3 == 0)
                total += 3;
            else if (y % 3 == 1)
                total += 2;
            else if (y % 3 == 2)
                total += 1;
            else
                total += 0;
        else if (x % 3 == 2)
            if (y % 3 == 0)
                total += 3;
            else if (y % 3 == 1)
                total += 2;
            else if (y % 3 == 2)
                total += 1;
            else
                total += 0;
        else
            if (y % 3 == 0)
                total += 3;
            else if (y % 3 == 1)
                total += 2;
            else if (y % 3 == 2)
                total += 1;
            else
                total += 0;

동등한 switch / case 문을 실행하는 데 걸린 시간의 절반으로 완료되었습니다 .

switch (x % 3)
    {
        case 0:
            switch (y % 3)
            {
                case 0: total += 3;
                    break;
                case 1: total += 2;
                    break;
                case 2: total += 1;
                    break;
                default: total += 0;
                    break;
            }
            break;
        case 1:
            switch (y % 3)
            {
                case 0: total += 3;
                    break;
                case 1: total += 2;
                    break;
                case 2: total += 1;
                    break;
                default: total += 0;
                    break;
            }
            break;
    case 2:
            switch (y % 3)
            {
                case 0: total += 3;
                    break;
                case 1: total += 2;
                    break;
                case 2: total += 1;
                    break;
                default: total += 0;
                    break;
            }
            break;
    default:
        switch (y % 3)
        {
            case 0: total += 3;
                break;
            case 1: total += 2;
                break;
            case 2: total += 1;
                break;
            default: total += 0;
                break;
        }
        break;
    }

예, 이것은 초보적인 예이지만 요점을 보여줍니다.

따라서 결론은 한 레벨 깊이의 단순 유형에 대해 스위치 / 케이스를 사용하는 것일 수 있지만 더 복잡한 비교 및 ​​다중 중첩 레벨의 경우 클래식 if / else 구문을 사용합니까?


-1 : 1. 기사는 분기 예측을 완전히 무시했습니다. 2. 알고리즘이 정확히 동일하지 않습니다 (링크의 단일 if-else가 이미 더 최적화되어 있음). 3. 발견 된 차이가 너무 작아서 변명 할 수 없습니다. 적절하고 깨끗한 코드 사용 (스위치와 동일한 if-else 구조 간의 호출 10.000.000 회에서 약 4ns)
Trojaner

이 예제는 스위치 블록에 몇 가지 사례가 있기 때문에 최적화되지 않습니다. 일반적으로 5 ~ 6 개의 요소 후에 점프 테이블이 생성됩니다.
antiduh

0

if over 케이스의 유일한 장점은 첫 번째 케이스의 발생 빈도가 눈에 띄게 증가 할 때입니다.

임계 값이 정확히 어디에 있는지 확실하지 않지만 첫 번째 "거의 항상"이 첫 번째 테스트를 통과하지 않는 한 케이스 구문을 사용합니다.

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