programing

'switch'가 'if'보다 빠릅니까?

yoursource 2022. 8. 10. 22:38
반응형

'switch'가 'if'보다 빠릅니까?

는 ?입니까?switch실제보다 빠른 성명if★★★★★★★★★★★★?

Studio 2010의 C++ 와 Visual Studio 2010의 x64 C 컴파일러로 했습니다./Ox 삭제:

#include <stdlib.h>
#include <stdio.h>
#include <time.h>

#define MAX_COUNT (1 << 29)
size_t counter = 0;

size_t testSwitch()
{
    clock_t start = clock();
    size_t i;
    for (i = 0; i < MAX_COUNT; i++)
    {
        switch (counter % 4 + 1)
        {
            case 1: counter += 4; break;
            case 2: counter += 3; break;
            case 3: counter += 2; break;
            case 4: counter += 1; break;
        }
    }
    return 1000 * (clock() - start) / CLOCKS_PER_SEC;
}

size_t testIf()
{
    clock_t start = clock();
    size_t i;
    for (i = 0; i < MAX_COUNT; i++)
    {
        const size_t c = counter % 4 + 1;
        if (c == 1) { counter += 4; }
        else if (c == 2) { counter += 3; }
        else if (c == 3) { counter += 2; }
        else if (c == 4) { counter += 1; }
    }
    return 1000 * (clock() - start) / CLOCKS_PER_SEC;
}

int main()
{
    printf("Starting...\n");
    printf("Switch statement: %u ms\n", testSwitch());
    printf("If     statement: %u ms\n", testIf());
}

다음과 같은 결과를 얻었습니다.

스테이트먼트: ms (스위치) : 5261 ms (스위치
스테이트먼트:: 5196 ms

배운 는요.switch확실히 점프 테이블을 사용하여 분기를 최적화합니다.

질문:

  1. x86 또는 x64의 기본 점프 테이블은 어떻게 생겼을까요?

  2. 이 코드는 점프 테이블을 사용하는 건가요?

  3. 이 예에서 성능 차이가 없는 이유는 무엇입니까?퍼포먼스에 큰 차이가 있는 상황이 있습니까?


코드 분해:

testIf:

13FE81B10 sub  rsp,48h 
13FE81B14 call qword ptr [__imp_clock (13FE81128h)] 
13FE81B1A mov  dword ptr [start],eax 
13FE81B1E mov  qword ptr [i],0 
13FE81B27 jmp  testIf+26h (13FE81B36h) 
13FE81B29 mov  rax,qword ptr [i] 
13FE81B2E inc  rax  
13FE81B31 mov  qword ptr [i],rax 
13FE81B36 cmp  qword ptr [i],20000000h 
13FE81B3F jae  testIf+0C3h (13FE81BD3h) 
13FE81B45 xor  edx,edx 
13FE81B47 mov  rax,qword ptr [counter (13FE835D0h)] 
13FE81B4E mov  ecx,4 
13FE81B53 div  rax,rcx 
13FE81B56 mov  rax,rdx 
13FE81B59 inc  rax  
13FE81B5C mov  qword ptr [c],rax 
13FE81B61 cmp  qword ptr [c],1 
13FE81B67 jne  testIf+6Dh (13FE81B7Dh) 
13FE81B69 mov  rax,qword ptr [counter (13FE835D0h)] 
13FE81B70 add  rax,4 
13FE81B74 mov  qword ptr [counter (13FE835D0h)],rax 
13FE81B7B jmp  testIf+0BEh (13FE81BCEh) 
13FE81B7D cmp  qword ptr [c],2 
13FE81B83 jne  testIf+89h (13FE81B99h) 
13FE81B85 mov  rax,qword ptr [counter (13FE835D0h)] 
13FE81B8C add  rax,3 
13FE81B90 mov  qword ptr [counter (13FE835D0h)],rax 
13FE81B97 jmp  testIf+0BEh (13FE81BCEh) 
13FE81B99 cmp  qword ptr [c],3 
13FE81B9F jne  testIf+0A5h (13FE81BB5h) 
13FE81BA1 mov  rax,qword ptr [counter (13FE835D0h)] 
13FE81BA8 add  rax,2 
13FE81BAC mov  qword ptr [counter (13FE835D0h)],rax 
13FE81BB3 jmp  testIf+0BEh (13FE81BCEh) 
13FE81BB5 cmp  qword ptr [c],4 
13FE81BBB jne  testIf+0BEh (13FE81BCEh) 
13FE81BBD mov  rax,qword ptr [counter (13FE835D0h)] 
13FE81BC4 inc  rax  
13FE81BC7 mov  qword ptr [counter (13FE835D0h)],rax 
13FE81BCE jmp  testIf+19h (13FE81B29h) 
13FE81BD3 call qword ptr [__imp_clock (13FE81128h)] 
13FE81BD9 sub  eax,dword ptr [start] 
13FE81BDD imul eax,eax,3E8h 
13FE81BE3 cdq       
13FE81BE4 mov  ecx,3E8h 
13FE81BE9 idiv eax,ecx 
13FE81BEB cdqe      
13FE81BED add  rsp,48h 
13FE81BF1 ret       

testSwitch:

13FE81C00 sub  rsp,48h 
13FE81C04 call qword ptr [__imp_clock (13FE81128h)] 
13FE81C0A mov  dword ptr [start],eax 
13FE81C0E mov  qword ptr [i],0 
13FE81C17 jmp  testSwitch+26h (13FE81C26h) 
13FE81C19 mov  rax,qword ptr [i] 
13FE81C1E inc  rax  
13FE81C21 mov  qword ptr [i],rax 
13FE81C26 cmp  qword ptr [i],20000000h 
13FE81C2F jae  testSwitch+0C5h (13FE81CC5h) 
13FE81C35 xor  edx,edx 
13FE81C37 mov  rax,qword ptr [counter (13FE835D0h)] 
13FE81C3E mov  ecx,4 
13FE81C43 div  rax,rcx 
13FE81C46 mov  rax,rdx 
13FE81C49 inc  rax  
13FE81C4C mov  qword ptr [rsp+30h],rax 
13FE81C51 cmp  qword ptr [rsp+30h],1 
13FE81C57 je   testSwitch+73h (13FE81C73h) 
13FE81C59 cmp  qword ptr [rsp+30h],2 
13FE81C5F je   testSwitch+87h (13FE81C87h) 
13FE81C61 cmp  qword ptr [rsp+30h],3 
13FE81C67 je   testSwitch+9Bh (13FE81C9Bh) 
13FE81C69 cmp  qword ptr [rsp+30h],4 
13FE81C6F je   testSwitch+0AFh (13FE81CAFh) 
13FE81C71 jmp  testSwitch+0C0h (13FE81CC0h) 
13FE81C73 mov  rax,qword ptr [counter (13FE835D0h)] 
13FE81C7A add  rax,4 
13FE81C7E mov  qword ptr [counter (13FE835D0h)],rax 
13FE81C85 jmp  testSwitch+0C0h (13FE81CC0h) 
13FE81C87 mov  rax,qword ptr [counter (13FE835D0h)] 
13FE81C8E add  rax,3 
13FE81C92 mov  qword ptr [counter (13FE835D0h)],rax 
13FE81C99 jmp  testSwitch+0C0h (13FE81CC0h) 
13FE81C9B mov  rax,qword ptr [counter (13FE835D0h)] 
13FE81CA2 add  rax,2 
13FE81CA6 mov  qword ptr [counter (13FE835D0h)],rax 
13FE81CAD jmp  testSwitch+0C0h (13FE81CC0h) 
13FE81CAF mov  rax,qword ptr [counter (13FE835D0h)] 
13FE81CB6 inc  rax  
13FE81CB9 mov  qword ptr [counter (13FE835D0h)],rax 
13FE81CC0 jmp  testSwitch+19h (13FE81C19h) 
13FE81CC5 call qword ptr [__imp_clock (13FE81128h)] 
13FE81CCB sub  eax,dword ptr [start] 
13FE81CCF imul eax,eax,3E8h 
13FE81CD5 cdq       
13FE81CD6 mov  ecx,3E8h 
13FE81CDB idiv eax,ecx 
13FE81CDD cdqe      
13FE81CDF add  rsp,48h 
13FE81CE3 ret       

업데이트:

흥미로운 결과입니다.하지만 왜 하나는 더 빠르고 하나는 더 느린지 모르겠다.

스위치로 컴파일러를 최적화할 수 있는 것은 몇 가지 있습니다.그러나 자주 언급되는 "점프 테이블"은 입력이 어떤 식으로든 제한될 수 있을 때만 작동하기 때문에 그다지 유용하다고 생각하지 않습니다.

"점프 테이블"의 유사 코드란 다음과 같습니다.실행 중인 컴파일러는 테이블 내에서 입력이 유효한지 확인하기 위해 테이블 주위에 if test를 삽입해야 합니다.또, 이 기능은, 연속하는 번호의 실행이라고 하는 특정의 경우에만 기능하는 것에 주의해 주세요.

스위치의 브랜치 수가 매우 많은 경우 컴파일러는 스위치의 값에 대해 바이너리 검색을 사용하는 등의 작업을 수행할 수 있습니다.이것은 (내 생각에) 일부 시나리오에서는 퍼포먼스가 크게 향상되고 스위치만큼 일반적이며 코드 크기가 커지지 않기 때문에 훨씬 더 유용한 최적화가 될 수 있습니다.그러나 이를 확인하려면 테스트 코드가 더 많은 브랜치를 필요로 합니다.

특정 질문에 답변하려면:

  1. Clang은 다음과 같은 것을 생성합니다.

    test_switch(char):                       # @test_switch(char)
            movl    %edi, %eax
            cmpl    $19, %edi
            jbe     .LBB0_1
            retq
    .LBB0_1:
            jmpq    *.LJTI0_0(,%rax,8)
            jmp     void call<0u>()         # TAILCALL
            jmp     void call<1u>()         # TAILCALL
            jmp     void call<2u>()         # TAILCALL
            jmp     void call<3u>()         # TAILCALL
            jmp     void call<4u>()         # TAILCALL
            jmp     void call<5u>()         # TAILCALL
            jmp     void call<6u>()         # TAILCALL
            jmp     void call<7u>()         # TAILCALL
            jmp     void call<8u>()         # TAILCALL
            jmp     void call<9u>()         # TAILCALL
            jmp     void call<10u>()        # TAILCALL
            jmp     void call<11u>()        # TAILCALL
            jmp     void call<12u>()        # TAILCALL
            jmp     void call<13u>()        # TAILCALL
            jmp     void call<14u>()        # TAILCALL
            jmp     void call<15u>()        # TAILCALL
            jmp     void call<16u>()        # TAILCALL
            jmp     void call<17u>()        # TAILCALL
            jmp     void call<18u>()        # TAILCALL
            jmp     void call<19u>()        # TAILCALL
    .LJTI0_0:
            .quad   .LBB0_2
            .quad   .LBB0_3
            .quad   .LBB0_4
            .quad   .LBB0_5
            .quad   .LBB0_6
            .quad   .LBB0_7
            .quad   .LBB0_8
            .quad   .LBB0_9
            .quad   .LBB0_10
            .quad   .LBB0_11
            .quad   .LBB0_12
            .quad   .LBB0_13
            .quad   .LBB0_14
            .quad   .LBB0_15
            .quad   .LBB0_16
            .quad   .LBB0_17
            .quad   .LBB0_18
            .quad   .LBB0_19
            .quad   .LBB0_20
            .quad   .LBB0_21
    
  2. 점프 테이블을 사용하지 않는다고 말할 수 있습니다. 4가지 비교 지침이 명확하게 표시됩니다.

    13FE81C51 cmp  qword ptr [rsp+30h],1 
    13FE81C57 je   testSwitch+73h (13FE81C73h) 
    13FE81C59 cmp  qword ptr [rsp+30h],2 
    13FE81C5F je   testSwitch+87h (13FE81C87h) 
    13FE81C61 cmp  qword ptr [rsp+30h],3 
    13FE81C67 je   testSwitch+9Bh (13FE81C9Bh) 
    13FE81C69 cmp  qword ptr [rsp+30h],4 
    13FE81C6F je   testSwitch+0AFh (13FE81CAFh) 
    

    점프 테이블 기반 솔루션은 비교를 전혀 사용하지 않습니다.

  3. 컴파일러가 점프 테이블을 생성하기에 충분한 브랜치가 없거나 컴파일러가 단순히 그것들을 생성하지 않습니다.어느 쪽인지 잘 모르겠어요.

2014년 편집:LLVM 옵티마이저를 잘 아는 사람들로부터 많은 시나리오에서 점프 테이블 최적화가 중요할 수 있다고 말하는 논의가 있었다. 예를 들어, 많은 값을 가진 열거형이 있고 해당 열거형의 값에 대해 많은 사례가 있는 경우.하지만 2011년에는 위에서 말한 것을 고수하고 있습니다.많은 케이스가 있어도 바꿀 수 있다고 생각하는 사람을 자주 볼 수 있습니다.이것은 완전히 잘못된 것입니다.점프 테이블을 사용하더라도 간접 점프 비용이 발생하며 각 케이스에 대해 표의 엔트리에 대한 비용을 지불해야 합니다. 메모리 대역폭은 최신 하드웨어에서 큰 문제입니다.

읽기 쉽도록 코드를 씁니다.가치가 있는 컴파일러라면 누구나 if/else를 확인하고 더 빠른 경우 동등한 스위치로 변환하거나 그 반대도 마찬가지입니다.

질문에 대한 답변:

1. x86 또는 x64의 기본 점프 테이블은 어떻게 생겼습니까?

점프 테이블은 배열 구조와 같은 레이블에 대한 포인터를 유지하는 메모리 주소입니다.다음 예시는 점프 테이블이 어떻게 배치되어 있는지를 이해하는 데 도움이 될 것입니다.

00B14538  D8 09 AB 00 D8 09 AB 00 D8 09 AB 00 D8 09 AB 00  Ø.«.Ø.«.Ø.«.Ø.«.
00B14548  D8 09 AB 00 D8 09 AB 00 D8 09 AB 00 00 00 00 00  Ø.«.Ø.«.Ø.«.....
00B14558  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00B14568  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................

여기에 이미지 설명 입력

여기서 00B14538은 점프 테이블에 대한 포인터이며 D8 09 AB 00과 같은 값은 라벨 포인터를 나타냅니다.

2. 이 코드는 점프 테이블을 사용하고 있습니까?이 경우에는 안 돼요.

3. 이 예에서 성능 차이가 없는 이유는 무엇입니까?

두 경우 모두 명령어가 같고 점프 테이블이 없기 때문에 성능 차이는 없습니다.

4. 퍼포먼스에 큰 차이가 있는 상황이 있습니까?

if 체크 시퀀스가 매우 긴 경우 점프 테이블을 사용하면 성능이 향상됩니다(브런치/jmp 명령어는 거의 정확하게 예측하지 못하면 비용이 많이 듭니다). 단, 메모리 비용이 발생합니다.

모든 비교 명령의 코드도 어느 정도 크기가 있기 때문에 특히 32비트 포인터나 오프셋을 사용하면 단일 점프 테이블 룩업이 실행 파일에서 훨씬 더 큰 크기를 차지하지 않을 수 있습니다.

결론:컴파일러는 이러한 케이스에 충분히 대응하고 적절한 명령을 생성합니다.

다음은 오래된 벤치++ 벤치마크(현재는 찾기 어려운)의 결과입니다.

Test Name:   F000003                         Class Name:  Style
CPU Time:       0.781  nanoseconds           plus or minus     0.0715
Wall/CPU:        1.00  ratio.                Iteration Count:  1677721600
Test Description:
 Time to test a global using a 2-way if/else if statement
 compare this test with F000004

Test Name:   F000004                         Class Name:  Style
CPU Time:        1.53  nanoseconds           plus or minus     0.0767
Wall/CPU:        1.00  ratio.                Iteration Count:  1677721600
Test Description:
 Time to test a global using a 2-way switch statement
 compare this test with F000003

Test Name:   F000005                         Class Name:  Style
CPU Time:        7.70  nanoseconds           plus or minus      0.385
Wall/CPU:        1.00  ratio.                Iteration Count:  1677721600
Test Description:
 Time to test a global using a 10-way if/else if statement
 compare this test with F000006

Test Name:   F000006                         Class Name:  Style
CPU Time:        2.00  nanoseconds           plus or minus     0.0999
Wall/CPU:        1.00  ratio.                Iteration Count:  1677721600
Test Description:
 Time to test a global using a 10-way switch statement
 compare this test with F000005

Test Name:   F000007                         Class Name:  Style
CPU Time:        3.41  nanoseconds           plus or minus      0.171
Wall/CPU:        1.00  ratio.                Iteration Count:  1677721600
Test Description:
 Time to test a global using a 10-way sparse switch statement
 compare this test with F000005 and F000006

서 알 수 있는 x 64 의 ) 각각 VC++ 9.0 x 64 의 입니다.if0.7로 하다테스트 횟수가 증가함에 따라 시간은 거의 선형적으로 조정됩니다.

switch 문을 사용하면 값이 고밀도인 한 양방향 테스트와 10방향 테스트 간에 속도 차이는 거의 없습니다.sparse 값을 사용한10-way 테스트는 dense 값을 사용한10-way 테스트의 약 1.6배 시간이 소요되지만 sparse 값을 사용한 경우에도 10-way의 2배 이상의 속도가 소요됩니다.if/else if.

결론: 4방향 테스트만 사용해도 실제는 퍼포먼스를 알 수 없습니다.switch »if/else이 코드의 수치를 보면, 4방향 테스트의 경우, 두 가지 결과가 상당히 비슷할 것으로 예상된다는 사실을 보간하는 것은 매우 쉽습니다(약 2.8나노초).if/else~ 2.0(의), ~ 2.0(표준)switch를 참조해 주세요.

컴파일러는 if 스테이트먼트와 동등한 코드로 switch 스테이트먼트를 컴파일하거나 점프 테이블을 작성할 수 있습니다.컴파일러 옵션에서 지정한 내용에 따라 가장 빠르게 실행되거나 가장 작은 코드를 생성할 수 있습니다.따라서 최악의 경우 if-statement와 같은 속도가 됩니다.

나는 컴파일러가 최선의 선택을 하고 코드를 가장 잘 읽을 수 있는 것에 초점을 맞출 것이라고 믿는다.

케이스 수가 매우 많아지면 일련의 if보다 점프 테이블이 훨씬 빨라진다.그러나 값 사이의 스텝이 매우 클 경우 점프 테이블이 커질 수 있으며 컴파일러는 이를 생성하지 않을 수도 있습니다.

스위치 테스트루프 중에는 테스트와 무관한 작업을 컴퓨터가 실행하지 않고 if 테스트루프 중에는 작업을 적게 실행했는지 어떻게 알 수 있습니까?테스트 결과에는 다음과 같이 표시되지 않습니다.

  1. 차이는 매우 작다
  2. 일련의 결과가 아니라 하나의 결과만 있다
  3. 사례가 너무 적다

결과:

덧붙였습니다.

printf("counter: %u\n", counter);

당신의 예에서는 카운터가 전혀 사용되지 않았기 때문에 루프를 최적화하지 않도록 끝까지 컴파일러가 루프를 실행하는 이유는 무엇입니까?즉시 스위치는 이러한 마이크로 벤치마크에도 항상 우위를 점하고 있었습니다.

코드의 다른 문제는 다음과 같습니다.

switch (counter % 4 + 1)

스위치 루프 내,

const size_t c = counter % 4 + 1; 

if 루프에 있습니다.그걸 고친다면 아주 큰 차이가 날 거야switch 스테이트먼트 안에 스테이트먼트를 넣는 것은 컴파일러가 값을 스택에 먼저 올리는 것이 아니라 CPU 레지스터에 직접 송신하도록 자극한다고 생각합니다.따라서 이는 스위치스테이트먼트에 유리하며 밸런스테스트가 아닙니다.

아, 그리고 시험 사이에 카운터를 재설정해야 할 것 같아요.실제로는 +1, +2, +3 등보다 난수를 사용하는 것이 좋습니다.왜냐하면 이 값은 최적화되기 때문입니다.난수란 예를 들어 현재 시간을 기준으로 한 숫자를 말합니다.그렇지 않으면 컴파일러는 두 함수를 모두 하나의 긴 연산으로 변환하여 루프를 발생시키지 않을 수 있습니다.

나는 겨우기 전에 코드를 운영한 컴파일러가 일을 해결할 수 없도록 하기 위해:라이언의 코드 변형시켜 왔다.

#include <stdlib.h>
#include <stdio.h>
#include <time.h>

#define MAX_COUNT (1 << 26)
size_t counter = 0;

long long testSwitch()
{
    clock_t start = clock();
    size_t i;
    for (i = 0; i < MAX_COUNT; i++)
    {
        const size_t c = rand() % 20 + 1;

        switch (c)
        {
                case 1: counter += 20; break;
                case 2: counter += 33; break;
                case 3: counter += 62; break;
                case 4: counter += 15; break;
                case 5: counter += 416; break;
                case 6: counter += 3545; break;
                case 7: counter += 23; break;
                case 8: counter += 81; break;
                case 9: counter += 256; break;
                case 10: counter += 15865; break;
                case 11: counter += 3234; break;
                case 12: counter += 22345; break;
                case 13: counter += 1242; break;
                case 14: counter += 12341; break;
                case 15: counter += 41; break;
                case 16: counter += 34321; break;
                case 17: counter += 232; break;
                case 18: counter += 144231; break;
                case 19: counter += 32; break;
                case 20: counter += 1231; break;
        }
    }
    return 1000 * (long long)(clock() - start) / CLOCKS_PER_SEC;
}

long long testIf()
{
    clock_t start = clock();
    size_t i;
    for (i = 0; i < MAX_COUNT; i++)
    {
        const size_t c = rand() % 20 + 1;
        if (c == 1) { counter += 20; }
        else if (c == 2) { counter += 33; }
        else if (c == 3) { counter += 62; }
        else if (c == 4) { counter += 15; }
        else if (c == 5) { counter += 416; }
        else if (c == 6) { counter += 3545; }
        else if (c == 7) { counter += 23; }
        else if (c == 8) { counter += 81; }
        else if (c == 9) { counter += 256; }
        else if (c == 10) { counter += 15865; }
        else if (c == 11) { counter += 3234; }
        else if (c == 12) { counter += 22345; }
        else if (c == 13) { counter += 1242; }
        else if (c == 14) { counter += 12341; }
        else if (c == 15) { counter += 41; }
        else if (c == 16) { counter += 34321; }
        else if (c == 17) { counter += 232; }
        else if (c == 18) { counter += 144231; }
        else if (c == 19) { counter += 32; }
        else if (c == 20) { counter += 1231; }
    }
    return 1000 * (long long)(clock() - start) / CLOCKS_PER_SEC;
}

int main()
{
    srand(time(NULL));
    printf("Starting...\n");
    printf("Switch statement: %lld ms\n", testSwitch()); fflush(stdout);
    printf("counter: %d\n", counter);
    counter = 0;
    srand(time(NULL));
    printf("If     statement: %lld ms\n", testIf()); fflush(stdout);
    printf("counter: %d\n", counter);
} 

: 3740
:3980 †

(여러 시도에 비슷한 결과).

나는 또한 5에 스위치가 기능 여전히 승리 cases/ifs의 수를 감소시켰다.

유의한 스위치는 점프 테이블을 아껴 먹는 것이 아닌 것은다면 당신은 자주 쓸 수 있지만, 좀 더 효율적으로 스위치보다...

만약 사례에 모든 N의 값은 최악의 경우 시험보다는 주문하는,(1), 당신은 위 또는 아래 반으로 그것의 각 반으로 당신의 의심을 테스트하기 위해 만일, 그렇다면, 2진 검색 스타일 써서...최악의 경우에 야기한 것 logN보다는 N.

만약 cases/groups 다른 케이스보다 빈번하다, 그럼 일단은 평균 시간을 통해 속도를 높일 수 있는 경우 격리할 수 있도록 당신의 가의 디자인(2).

MSVC과 같은 좋은 최적화 컴파일러:발생할 수 있다.

  1. 단순한 점프 테이블을 만약 그 사례들은 좋은 긴 범위에 가지런히 놓여 있다.
  2. A보다(2단계)점프 테이블을 그 곳에 많은 차이가 있다는 것입니다.
  3. 만약에 일련의 경우 사건의 수 또는 값이 가까운 함께 작다.
  4. 위의 조합이 경우closely-spaced 범위의 여러 단체들을 나타낸다.

만약 스위치를 조건이 느리다고 보인다에, 컴파일러는 하나일 뿐으로 변환할 수 있다.그리고 각각의 경우에 대해서 비교한 시퀀스지만, 이진 탐색 트리일 가능성이 높아.예에 대해서는, 여기를 참조해 주세요.

2)에 답하여 일반적인 코멘트를 하겠습니다.2) 아니요, 당신이 게재한 어셈블리 코드에는 점프 테이블이 없습니다.점프 테이블은 점프 행선지의 테이블로 테이블에서 인덱스된 위치로 직접 점프하는 명령 1개 또는2개입니다.스위치 수신처가 많은 경우 점프 테이블이 더 적합합니다.옵티마이저는, 행선지의 수가 임계치보다 크지 않는 한, 심플한 로직이 고속인 것을 알고 있을 가능성이 있습니다.예를 들어 4가지 대신 20가지 가능성을 제시하여 다시 시도해 보십시오.

저는 호기심에 끌려서 당신의 예에서 switch 문을 더 빨리 실행하기 위해 바꿀 수 있는 점을 살펴보았습니다.

if 스테이트먼트가 40이 되어 0 대소문자가 추가되면 if 블록은 동등한 스위치스테이트먼트보다 느리게 실행됩니다.결과는 https://www.ideone.com/KZeCz에 있습니다.

0 케이스를 삭제했을 경우의 효과는, https://www.ideone.com/LFnrX 에서 확인할 수 있습니다.

아니, 점프하면 점프하면...점프 테이블에는 주소 테이블이 있거나 해시 같은 것이 사용됩니다.

빠르거나 느리는 것은 주관적입니다.예를 들어 첫 번째가 아니라 케이스1이 마지막이 될 수 있습니다.테스트 프로그램이나 실제 프로그램이 케이스1을 사용하는 경우는, 이 실장에서는 코드가 늦어지는 경우가 대부분입니다.따라서 구현에 따라 사례 목록을 다시 정렬하는 것만으로 큰 차이를 만들 수 있습니다.

1-4가 아닌 케이스 0-3을 사용했다면 컴파일러가 점프 테이블을 사용했을 가능성이 있습니다.어차피 +1을 삭제한 것을 컴파일러가 알아냈을 것입니다.아마 물건의 수가 적었기 때문일 겁니다.예를 들어 0 - 15 또는 0 - 31로 했을 경우 테이블과 함께 구현하거나 다른 단축키를 사용했을 수 있습니다.컴파일러는 소스 코드의 기능을 만족하는 한 구현 방법을 자유롭게 선택할 수 있습니다.컴파일러의 차이, 버전 차이, 최적화 차이 등이 여기에 포함됩니다.점프 테이블을 원하면 점프 테이블을 만들고, if-then-else 트리를 만들려면 if-then-else 트리를 만듭니다.컴파일러가 결정하도록 하려면 switch/case 스테이트먼트를 사용합니다.

하지만 왜 하나는 더 빠르고 하나는 더 느린지 모르겠다.

사실 설명하기가 그렇게 어렵진 않은데...잘못 예측된 브랜치는 올바르게 예측된 브랜치보다 수십~수백 배 비쌉니다.

에서% 20version, 첫 번째 대소문자와 if가 항상 히트합니다.최신 CPU는 보통 어떤 브랜치를 사용하고 어떤 브랜치를 사용하지 않는지 "학습"하기 때문에 루프의 거의 모든 반복에서 이 브랜치가 어떻게 동작할지를 쉽게 예측할 수 있습니다.즉, 첫 번째 테스트를 통과한 후에는 아무것도 실행할 필요가 없으며 대부분의 반복에 대해 테스트 결과를 정확하게 예측합니다.확실히 '스위치'는 약간 다르게 구현됩니다.점프 테이블도 마찬가지입니다.계산된 브랜치 때문에 속도가 느려질 수 있습니다.

에서% 21버전, 브랜치는 기본적으로 랜덤입니다.따라서 이들 중 많은 수가 반복할 때마다 실행될 뿐만 아니라 CPU는 어느 방향으로 진행할지 예측할 수 없습니다.이것은 점프 테이블(또는 다른 "스위치" 최적화)이 도움이 될 가능성이 높은 경우입니다.

현대의 컴파일러와 CPU에서는 코드 조각이 어떻게 동작할지를 예측하는 것은 매우 어렵습니다.또, 세대 마다, 한층 더 어려워지고 있습니다.가장 좋은 조언은 "노력하지 말고 항상 프로파일링하세요"입니다.그 조언은 매년 개선되고 있습니다.그리고 이를 성공적으로 무시할 수 있는 사람들의 수는 점점 줄어들고 있습니다.

위의 설명은 어디까지나 추측입니다. :-)

없습니다. 대부분의 경우 조립업체에서 실제 성능 측정을 수행할 때 질문은 잘못된 것입니다.예를 들어, 당신의 생각은 확실히 너무 짧다.

counter += (4 - counter % 4);

사용하는 것이 올바른 증분 표현인 것 같습니다.

언급URL : https://stackoverflow.com/questions/6805026/is-switch-faster-than-if

반응형