c / c ++에 다른 경우 스위치가 체인과 같은 방식으로 최적화되지 않는 이유는 무엇입니까?


39

다음 square 구현은 체인 if 문과 같이 cmp / je 문을 생성합니다.

int square(int num) {
    if (num == 0){
        return 0;
    } else if (num == 1){
        return 1;
    } else if (num == 2){
        return 4;
    } else if (num == 3){
        return 9;
    } else if (num == 4){
        return 16;
    } else if (num == 5){
        return 25;
    } else if (num == 6){
        return 36;
    } else if (num == 7){
        return 49;
    } else {
        return num * num;
    }
}

그리고 다음은 반환 할 데이터 테이블을 생성합니다.

int square_2(int num) {
    switch (num){
        case 0: return 0;
        case 1: return 1;
        case 2: return 4;
        case 3: return 9;
        case 4: return 16;
        case 5: return 25;
        case 6: return 36;
        case 7: return 49;
        default: return num * num;
    }
}

왜 gcc가 상단을 하단으로 최적화 할 수 없습니까?

참조를위한 분해 : https://godbolt.org/z/UP_igi

편집 : 흥미롭게도 MSVC는 스위치 케이스의 데이터 테이블 대신 점프 테이블을 생성합니다. 놀랍게도, clang은 그것들을 동일한 결과로 최적화합니다.


3
"정의되지 않은 행동"이란 무엇입니까? 관찰 가능한 동작이 동일하다면 컴파일러는 원하는 어셈블리 / 기계 코드를 생성 할 수 있습니다.
bolov

2
returns를 무시하고 @ user207421 ; 이 경우에는 no breaks가 있으므로 스위치도 특정 실행 순서를 갖습니다. if / else 체인은 모든 브랜치에서 리턴을 가지며,이 경우 시맨틱은 동일합니다. 최적화는 불가능 하지 않습니다 . 이에 반해 icc 는 어떤 기능도 최적화하지 않습니다.
user1810087

9
아마도 가장 간단한 대답은 ... gcc는이 구조를 보지 못하고 최적화 할 수는 없습니다 (아직).
user1810087

3
@ user1810087에 동의합니다. 컴파일러 개선 프로세스의 현재 경계를 발견했습니다. 현재 일부 컴파일러에서 최적화 가능한 것으로 인식되지 않는 하위 하위 사례. 실제로, 다른 모든 체인은 그러한 방식으로 최적화 될 수있는 것은 아니지만, 상수 값에 대해 SAME 변수가 테스트되는 서브 세트 만 가능합니다.
로베르토 카 보니

1
if-else는 위에서 아래로 실행 순서가 다릅니다. 여전히 if 문으로 코드를 바꾸면 머신 코드가 향상되지 않습니다. 반면 스위치는 사전 정의 된 실행 순서가 없으며 본질적으로 영광스러운 goto jump 테이블입니다. 즉, 컴파일러는 여기에서 관찰 가능한 동작에 대해 추론 할 수 있으므로 if-else 버전의 최적화가 매우 실망 스럽습니다.
Lundin

답변:


29

일반적으로 생성 된 코드 switch-case는 점프 테이블을 사용합니다. 이 경우, 룩업 테이블을 통한 직접 리턴은 여기에있는 모든 경우에 리턴이 포함된다는 사실을 이용하여 최적화하는 것 같습니다. 표준이 그 효과를 보장하지는 않지만, 컴파일러가 일반적인 스위치 케이스에 대한 점프 테이블 대신 일련의 비교를 생성한다면 놀랄 것입니다.

지금오고 if-else, 그것은 정반대입니다. switch-case분기 수에 관계없이 일정한 시간에 실행되는 동안 if-else더 적은 수의 분기에 최적화됩니다. 여기에서 컴파일러는 기본적으로 일련의 비교를 작성한 순서대로 생성 할 것으로 예상합니다.

나는 사용했다 그렇다면 if-else나는 대부분의 호출에 대한 기대 때문에 square()이 될하는 0또는 1드물게 다른 값을 후 실제로 내 코드가 사용에 대한 내 목적을 물리 치고, 느린 내가 예상 한 것보다 실행할 수있는 원인 테이블 - 조회에이 '최적화' if대신 (A)의 switch. 따라서 논쟁의 여지가 있지만 GCC가 올바른 일을하고 있으며 clang이 최적화에 지나치게 공격적이라고 생각합니다.

어떤 사람은 의견에서 clang 이이 최적화를 수행 하고 조회 테이블 기반 코드를 생성 하는 링크를 공유했습니다 if-else. clang을 사용하여 사례 수를 2 개 (및 기본값)로 줄이면 주목할만한 일이 발생합니다. if와 switch 모두 동일한 코드를 다시 생성하지만 이번에 는 lookup-table 방식 대신 비교 및 이동으로 전환됩니다 . 즉, 스위치 선호 클랜도 케이스 수가 적을 때 'if'패턴이 더 최적이라는 것을 알고 있습니다!

요약하면, 일련의 비교 if-else및 점프 테이블 switch-case은 컴파일러가 따르는 경향이 있고 개발자가 코드를 작성할 때 기대하는 표준 패턴입니다. 그러나 특정 특수한 경우 일부 컴파일러는 더 나은 최적화를 제공한다고 생각되는 위치에서이 패턴을 중단하기로 선택할 수 있습니다. 다른 컴파일러는 아마도 최적이 아닌 경우에도 패턴을 고수하여 개발자가 원하는 것을 알도록 할 수 있습니다. 둘 다 고유 한 장단점이있는 유효한 접근 방식입니다.


2
그렇습니다. 최적화는 여러 가지 측면을 가진 칼입니다. 그들이 쓰는 것, 원하는 것, 얻는 것, 저주를받는 사람.
중복 제거기

1
"... 그러면 이것을 테이블 조회에 '최적화'하면 실제로 코드가 예상보다 느리게 실행됩니다 ..." 이것에 대한 정당성을 제공 할 수 있습니까? 왜 점프 테이블 이제까지보다 느린 것 이 개 가능한 조건 분기 (에 대한 입력을 확인 0하고 1)?
코디 그레이

@CodyGray 계산 사이클 수준에 도달하지 않았다고 고백해야합니다. 포인터를 통한 메모리의로드가 비교 및 ​​점프보다 더 많은 사이클이 걸릴 수 있다는 느낌이 들었습니다.하지만 잘못 될 수 있습니다. 그러나이 경우에도 적어도 '0'에 if대해서는 분명히 더 빠르다는 것에 동의합니다 . 다음은 if스위치를 사용할 때보 다 0과 1이 더 빠를 수있는 플랫폼의 예입니다 . godbolt.org/z/wcJhvS (여기에 다른 여러 최적화가 있습니다)
th33lf

1
어쨌든 현대 슈퍼 스칼라 OOO 아키텍처에서는 카운팅 사이클이 작동하지 않습니다. :-) 메모리로부터의로드는 잘못 예측 된 브랜치보다 느리지 않을 것이므로 문제는 브랜치가 얼마나 예측 될 수 있을까요? 이 질문은 명시 적 if명령문에 의해 생성 되거나 컴파일러에 의해 자동으로 생성되는지 여부에 관계없이 모든 조건부 분기에 적용됩니다 . 저는 ARM 전문가가 아니기 때문에 귀하가 주장하는 것이 사실 switch보다 더 빠르다 if는 사실 은 확실하지 않습니다 . 잘못 예측 된 브랜치의 페널티에 따라 달라지며 실제로 ARM에 따라 다릅니다 .
코디 그레이

0

가능한 근거 중 하나는 값이 낮을수록 ( num예 : 항상 0) 첫 번째 코드에 대해 생성 된 코드가 더 빠를 수 있다는 것입니다. 스위치에 대해 생성 된 코드는 모든 값에 대해 동일한 시간이 걸립니다.

에 따라, 최적의 사례를 비교 이 표 . 표에 대한 설명은 이 답변 을 참조하십시오 .

만약 num == 0에 당신이 XOR, 테스트, (점프) JE이 "만일", RET. 지연 시간 : 1 + 1 + 점프. 그러나 xor와 test는 독립적이므로 실제 실행 속도는 1 + 1 사이클보다 빠릅니다.

만약 num < 7, 당신이 MOV, CMP, (점프없이) JA, MOV, RET이 "스위치"를 참조하십시오. 지연 시간 : 2 + 1 + 점프 없음 + 2.

점프하지 않는 점프 명령이 점프하는 명령보다 빠릅니다. 그러나 테이블은 점프에 대한 대기 시간을 정의하지 않으므로 어느 것이 더 나은지 명확하지 않습니다. 마지막 것이 항상 더 좋을 수 있으며 GCC는 단순히 그것을 최적화 할 수 없습니다.


1
흠, 흥미로운 이론이지만 ifs vs switch의 경우 xor, test, jmp vs mov, cmp jmp가 있습니다. 마지막 명령은 각각 3 개의 명령입니다. 가장 좋은 경우에는 동일하게 보입니까?
chacham15

3
"점프 명령이 점프 명령보다 빠릅니다." 중요한 분기 예측입니다.
geza
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.