developer tip

if 문과 if-else 문 중 어느 것이 더 빠릅니까?

optionbox 2020. 10. 6. 08:07
반응형

if 문과 if-else 문 중 어느 것이 더 빠릅니까? [닫은]


나는 요 전에 그 두 조각에 대해 친구와 논쟁했습니다. 어느 것이 더 빠르고 왜?

value = 5;
if (condition) {
    value = 6;
}

과:

if (condition) {
    value = 6;
} else {
    value = 5;
}

만약에 value매트릭스는?

참고 : 그게 value = condition ? 6 : 5;존재 한다는 것을 알고 있고 더 빠를 것으로 기대하지만 옵션이 아닙니다.

수정 (현재 질문이 보류 중이므로 직원이 요청 함) :

  • 최적화 된 버전과 최적화되지 않은 버전 모두에서 주류 컴파일러 ( 예 : g ++, clang ++, vc, mingw )에 의해 생성 된 x86 어셈블리 또는 MIPS 어셈블리 를 고려하여 답변하십시오 .
  • 어셈블리가 다를 때 버전이 더 빠른 이유와시기를 설명합니다 ( 예 : "Branching 및 분기에 다음 문제가 발생하지 않기 때문에 더 좋음" ).

요약 : 최적화되지 않은 코드에서 if없이는 else관련이 없을 정도로 더 효율적으로 보이지만 가장 기본적인 수준의 최적화를 활성화하면 코드가 기본적으로 value = condition + 5.


나는 그것을 시도 하고 다음 코드에 대한 어셈블리를 생성했습니다.

int ifonly(bool condition, int value)
{
    value = 5;
    if (condition) {
        value = 6;
    }
    return value;
}

int ifelse(bool condition, int value)
{
    if (condition) {
        value = 6;
    } else {
        value = 5;
    }
    return value;
}

최적화가 비활성화 된 gcc 6.3 ( -O0)에서 관련 차이점은 다음과 같습니다.

 mov     DWORD PTR [rbp-8], 5
 cmp     BYTE PTR [rbp-4], 0
 je      .L2
 mov     DWORD PTR [rbp-8], 6
.L2:
 mov     eax, DWORD PTR [rbp-8]

에 대한 ifonly반면이 ifelse있다

 cmp     BYTE PTR [rbp-4], 0
 je      .L5
 mov     DWORD PTR [rbp-8], 6
 jmp     .L6
.L5:
 mov     DWORD PTR [rbp-8], 5
.L6:
 mov     eax, DWORD PTR [rbp-8]

후자는 추가 점프가 있기 때문에 약간 덜 효율적으로 보이지만 둘 다 최소 2 개와 최대 3 개의 할당이 있으므로 마지막 성능 저하를 모두 짜낼 필요가없는 한 (힌트 : 우주 왕복선에서 작업하지 않는 한 , 그리고 당신은 아마 그 차이가 눈에 띄지 않을 것입니다.

그러나 최적화 수준이 가장 낮더라도 ( -O1) 두 함수는 모두 동일하게 감소합니다.

test    dil, dil
setne   al
movzx   eax, al
add     eax, 5

기본적으로 다음과 같습니다.

return 5 + condition;

condition0 또는 1 이라고 가정합니다 . 더 높은 최적화 수준은 처음에 레지스터를 movzx효율적으로 제로화 하여이를 방지하는 것을 제외하고는 실제로 출력을 변경하지 않습니다 EAX.


면책 조항 : 당신 의 의도가 당신의 코드를 읽는 사람들에게 즉각적으로 분명하지 않을 수 있기 때문에 ( 정수 유형으로의 5 + condition변환이 true를 제공 한다고 표준이 보장하더라도) 당신은 당신 자신을 작성해서는 안됩니다 1. 이 코드의 요점은 컴파일러가 두 경우 모두에서 생성하는 것이 (실질적으로) 동일하다는 것을 보여주는 것입니다. Ciprian Tomoiaga 는 의견에서이를 아주 잘 설명합니다.

인간 의 작업은 코드를 작성하는 것입니다 인간에 대한 그리고하자 컴파일러 에 대한 코드를 작성 기계를 .


에서 대답 CompuChip에 대한 쇼는 int그들이 모두 같은 어셈블리에 최적화 된, 그래서 그것은 중요하지 않습니다.

값이 행렬이면 어떨까요?

나는 이것을 좀 더 일반적인 방식으로 해석 할 것입니다. 즉 value, 구조와 할당이 비싸고 이동이 저렴한 유형 이라면 어떨까요?

그때

T value = init1;
if (condition)
   value = init2;

conditiontrue 인 경우 불필요한 초기화를 init1수행 한 다음 복사 할당을 수행 하기 때문에 차선책 입니다.

T value;
if (condition)
   value = init2;
else
   value = init3;

이게 낫다. 그러나 기본 구성이 비싸고 복사 구성이 초기화보다 더 비싸다면 여전히 차선책입니다.

좋은 조건부 연산자 솔루션이 있습니다.

T value = condition ? init1 : init2;

또는 조건 연산자가 마음에 들지 않으면 다음과 같은 도우미 함수를 만들 수 있습니다.

T create(bool condition)
{
  if (condition)
     return {init1};
  else
     return {init2};
}

T value = create(condition);

무엇 init1이며 무엇인지에 따라 다음을 init2고려할 수도 있습니다.

auto final_init = condition ? init1 : init2;
T value = final_init;

그러나 나는 이것이 주어진 유형에 대해 건설 및 할당이 정말로 비쌀 때만 관련이 있음을 강조해야합니다. 그런 다음에도 프로파일 링통해서만 확실히 알 수 있습니다.


의사 어셈블리 언어에서

    li    #0, r0
    test  r1
    beq   L1
    li    #1, r0
L1:

보다 빠르 거나 빠르지 않을 수 있습니다.

    test  r1
    beq   L1
    li    #1, r0
    bra   L2
L1:
    li    #0, r0
L2:

실제 CPU가 얼마나 정교한 지에 따라 다릅니다. 가장 단순한 것에서 가장 공상적인 것까지 :

  • 으로 어떤 CPU가 대략 1990 이후에 제조 된, 좋은 성능은 내 피팅 코드에 따라 명령어 캐시 . 따라서 의심스러운 경우 코드 크기를 최소화하십시오. 이것은 첫 번째 예에 유리합니다.

  • 여전히 많은 마이크로 컨트롤러에서 얻을 수 있는 기본 " 순서대로, 5 단계 파이프 라인 "CPU를 사용 하면 분기 (조건부 또는 무조건 부)를 취할 때마다 파이프 라인 버블 이 발생하므로 최소화하는 것도 중요합니다. 분기 명령의 수. 이것은 또한 첫 번째 예에 유리합니다.

  • 다소 더 정교한 CPU ( " 순서에 맞지 않는 실행 "을 수행 할 수있을만큼 훌륭하지만 해당 개념의 가장 잘 알려진 구현을 사용할만큼 공상적이지 않음)는 쓰기 후 쓰기 위험이 발생할 때마다 파이프 라인 버블을 일으킬 수 있습니다 . 이것은 두 번째에 유리하며, r0어떤 경우에도 한 번만 작성됩니다. 이러한 CPU는 일반적으로 명령어 페처에서 무조건 분기를 처리 할 수있을만큼 멋지므로 쓰기 후 쓰기 패널티를 분기 패널티로 거래하는 것이 아닙니다 .

    더 이상 이런 종류의 CPU를 만드는 사람이 있는지 모르겠습니다. 그러나, CPU를 않습니다 당신이 이런 종류의 일이 일어날 수 있다는 것을 인식 할 필요가 있으므로 아웃 오브 오더 실행의 "가장 잘 알려진 구현"을 사용은 자주 사용 지침에 잘라 모서리 가능성이있다. 실제 예는 의 대상 레지스터에 잘못된 데이터 종속성 popcntlzcnt샌디 브릿지 CPU에서 .

  • 가장 높은 수준에서 OOO 엔진은 두 코드 조각에 대해 정확히 동일한 순서의 내부 작업을 실행합니다. 이것은 "걱정하지 마십시오. 컴파일러가 어느 쪽이든 동일한 기계 코드를 생성합니다"의 하드웨어 버전입니다. 그러나 코드 크기는 여전히 중요하며 이제 조건부 분기의 예측 가능성에 대해서도 걱정해야합니다. 분기 예측 실패는 잠재적으로 전체 파이프 라인 플러시를 유발하여 성능에 치명적입니다. 정렬되지 않은 배열보다 정렬 된 배열을 처리하는 것이 더 빠른 이유를 참조하십시오 . 이것이 얼마나 많은 차이를 만들 수 있는지 이해합니다.

    분기 매우 예측 불가능하고 CPU에 조건부 설정 또는 조건부 이동 명령이있는 경우 다음을 사용할 때입니다.

        li    #0, r0
        test  r1
        setne r0
    

    또는

        li    #0, r0
        li    #1, r2
        test  r1
        movne r2, r0
    

    조건부 설정 버전은 다른 어떤 대안보다 더 간결합니다. 해당 명령을 사용할 수있는 경우 분기가 예측 가능하더라도이 시나리오에 대한 올바른 것이 실제로 보장됩니다. 조건부 이동 버전에는 추가 스크래치 레지스터가 필요하며 항상 하나의 li명령어 분량의 디스패치 및 실행 리소스를 낭비 합니다. 분기가 실제로 예측 가능한 경우 분기 버전이 더 빠를 수 있습니다.


최적화되지 않은 코드에서 첫 번째 예제는 변수를 항상 한 번, 때로는 두 번 할당합니다. 두 번째 예는 변수를 한 번만 할당합니다. 조건부는 두 코드 경로에서 동일하므로 중요하지 않습니다. 최적화 된 코드에서는 컴파일러에 따라 다릅니다.

항상 그렇듯이, 그럴 경우 어셈블리를 생성하고 컴파일러가 실제로 수행하는 작업을 확인하십시오.


하나의 라이너가 더 빠르거나 느리다고 생각하는 이유는 무엇입니까?

unsigned int fun0 ( unsigned int condition, unsigned int value )
{
    value = 5;
    if (condition) {
        value = 6;
    }
    return(value);
}
unsigned int fun1 ( unsigned int condition, unsigned int value )
{

    if (condition) {
        value = 6;
    } else {
        value = 5;
    }
    return(value);
}
unsigned int fun2 ( unsigned int condition, unsigned int value )
{
    value = condition ? 6 : 5;
    return(value);
}

고급 언어의 코드 줄이 더 많으면 컴파일러가 더 많은 작업을 수행 할 수 있으므로 이에 대한 일반적인 규칙을 만들고 싶다면 컴파일러에 더 많은 코드를 사용할 수 있습니다. 알고리즘이 위의 경우와 같으면 최소한의 최적화로 컴파일러가이를 파악할 것으로 예상합니다.

00000000 <fun0>:
   0:   e3500000    cmp r0, #0
   4:   03a00005    moveq   r0, #5
   8:   13a00006    movne   r0, #6
   c:   e12fff1e    bx  lr

00000010 <fun1>:
  10:   e3500000    cmp r0, #0
  14:   13a00006    movne   r0, #6
  18:   03a00005    moveq   r0, #5
  1c:   e12fff1e    bx  lr

00000020 <fun2>:
  20:   e3500000    cmp r0, #0
  24:   13a00006    movne   r0, #6
  28:   03a00005    moveq   r0, #5
  2c:   e12fff1e    bx  lr

그러나 동일한 실행 시간에 다른 순서로 첫 번째 기능을 수행 한 것은 놀라운 일이 아닙니다.

0000000000000000 <fun0>:
   0:   7100001f    cmp w0, #0x0
   4:   1a9f07e0    cset    w0, ne
   8:   11001400    add w0, w0, #0x5
   c:   d65f03c0    ret

0000000000000010 <fun1>:
  10:   7100001f    cmp w0, #0x0
  14:   1a9f07e0    cset    w0, ne
  18:   11001400    add w0, w0, #0x5
  1c:   d65f03c0    ret

0000000000000020 <fun2>:
  20:   7100001f    cmp w0, #0x0
  24:   1a9f07e0    cset    w0, ne
  28:   11001400    add w0, w0, #0x5
  2c:   d65f03c0    ret

다른 구현이 실제로 다르지 않다는 것이 분명하지 않다면 방금 시도했을 수있는 아이디어를 얻었기를 바랍니다.

매트릭스에 관한 한 그게 얼마나 중요한지 잘 모르겠지만

if(condition)
{
 big blob of code a
}
else
{
 big blob of code b
}

코드의 큰 덩어리 주위에 동일한 if-then-else 래퍼를 넣을 것입니다. value = 5 또는 더 복잡한 것입니다. 마찬가지로 코드의 큰 덩어리 라 할지라도 비교는 여전히 계산되어야하며, 어떤 것과 같거나 같지 않은 것은 종종 부정으로 컴파일됩니다. if (condition) do something은 종종 condition goto가 아닌 것처럼 컴파일됩니다.

00000000 <fun0>:
   0:   0f 93           tst r15     
   2:   03 24           jz  $+8         ;abs 0xa
   4:   3f 40 06 00     mov #6, r15 ;#0x0006
   8:   30 41           ret         
   a:   3f 40 05 00     mov #5, r15 ;#0x0005
   e:   30 41           ret         

00000010 <fun1>:
  10:   0f 93           tst r15     
  12:   03 20           jnz $+8         ;abs 0x1a
  14:   3f 40 05 00     mov #5, r15 ;#0x0005
  18:   30 41           ret         
  1a:   3f 40 06 00     mov #6, r15 ;#0x0006
  1e:   30 41           ret         

00000020 <fun2>:
  20:   0f 93           tst r15     
  22:   03 20           jnz $+8         ;abs 0x2a
  24:   3f 40 05 00     mov #5, r15 ;#0x0005
  28:   30 41           ret         
  2a:   3f 40 06 00     mov #6, r15 ;#0x0006
  2e:   30 41

우리는 최근에 stackoverflow에서 다른 사람과이 연습을 수행했습니다. 이 밉 컴파일러는 흥미롭게도 함수가 동일하다는 것을 깨달았을뿐만 아니라 코드 공간을 절약하기 위해 한 함수가 다른 함수로 점프했습니다. 그래도 여기에서하지 않았다

00000000 <fun0>:
   0:   0004102b    sltu    $2,$0,$4
   4:   03e00008    jr  $31
   8:   24420005    addiu   $2,$2,5

0000000c <fun1>:
   c:   0004102b    sltu    $2,$0,$4
  10:   03e00008    jr  $31
  14:   24420005    addiu   $2,$2,5

00000018 <fun2>:
  18:   0004102b    sltu    $2,$0,$4
  1c:   03e00008    jr  $31
  20:   24420005    addiu   $2,$2,5

더 많은 표적.

00000000 <_fun0>:
   0:   1166            mov r5, -(sp)
   2:   1185            mov sp, r5
   4:   0bf5 0004       tst 4(r5)
   8:   0304            beq 12 <_fun0+0x12>
   a:   15c0 0006       mov $6, r0
   e:   1585            mov (sp)+, r5
  10:   0087            rts pc
  12:   15c0 0005       mov $5, r0
  16:   1585            mov (sp)+, r5
  18:   0087            rts pc

0000001a <_fun1>:
  1a:   1166            mov r5, -(sp)
  1c:   1185            mov sp, r5
  1e:   0bf5 0004       tst 4(r5)
  22:   0204            bne 2c <_fun1+0x12>
  24:   15c0 0005       mov $5, r0
  28:   1585            mov (sp)+, r5
  2a:   0087            rts pc
  2c:   15c0 0006       mov $6, r0
  30:   1585            mov (sp)+, r5
  32:   0087            rts pc

00000034 <_fun2>:
  34:   1166            mov r5, -(sp)
  36:   1185            mov sp, r5
  38:   0bf5 0004       tst 4(r5)
  3c:   0204            bne 46 <_fun2+0x12>
  3e:   15c0 0005       mov $5, r0
  42:   1585            mov (sp)+, r5
  44:   0087            rts pc
  46:   15c0 0006       mov $6, r0
  4a:   1585            mov (sp)+, r5
  4c:   0087            rts pc

00000000 <fun0>:
   0:   00a03533            snez    x10,x10
   4:   0515                    addi    x10,x10,5
   6:   8082                    ret

00000008 <fun1>:
   8:   00a03533            snez    x10,x10
   c:   0515                    addi    x10,x10,5
   e:   8082                    ret

00000010 <fun2>:
  10:   00a03533            snez    x10,x10
  14:   0515                    addi    x10,x10,5
  16:   8082                    ret

및 컴파일러

이 코드를 사용하면 다른 대상도 일치 할 것으로 예상합니다.

define i32 @fun0(i32 %condition, i32 %value) #0 {
  %1 = icmp ne i32 %condition, 0
  %. = select i1 %1, i32 6, i32 5
  ret i32 %.
}

; Function Attrs: norecurse nounwind readnone
define i32 @fun1(i32 %condition, i32 %value) #0 {
  %1 = icmp eq i32 %condition, 0
  %. = select i1 %1, i32 5, i32 6
  ret i32 %.
}

; Function Attrs: norecurse nounwind readnone
define i32 @fun2(i32 %condition, i32 %value) #0 {
  %1 = icmp ne i32 %condition, 0
  %2 = select i1 %1, i32 6, i32 5
  ret i32 %2
}


00000000 <fun0>:
   0:   e3a01005    mov r1, #5
   4:   e3500000    cmp r0, #0
   8:   13a01006    movne   r1, #6
   c:   e1a00001    mov r0, r1
  10:   e12fff1e    bx  lr

00000014 <fun1>:
  14:   e3a01006    mov r1, #6
  18:   e3500000    cmp r0, #0
  1c:   03a01005    moveq   r1, #5
  20:   e1a00001    mov r0, r1
  24:   e12fff1e    bx  lr

00000028 <fun2>:
  28:   e3a01005    mov r1, #5
  2c:   e3500000    cmp r0, #0
  30:   13a01006    movne   r1, #6
  34:   e1a00001    mov r0, r1
  38:   e12fff1e    bx  lr


fun0:
    push.w  r4
    mov.w   r1, r4
    mov.w   r15, r12
    mov.w   #6, r15
    cmp.w   #0, r12
    jne .LBB0_2
    mov.w   #5, r15
.LBB0_2:
    pop.w   r4
    ret

fun1:
    push.w  r4
    mov.w   r1, r4
    mov.w   r15, r12
    mov.w   #5, r15
    cmp.w   #0, r12
    jeq .LBB1_2
    mov.w   #6, r15
.LBB1_2:
    pop.w   r4
    ret


fun2:
    push.w  r4
    mov.w   r1, r4
    mov.w   r15, r12
    mov.w   #6, r15
    cmp.w   #0, r12
    jne .LBB2_2
    mov.w   #5, r15
.LBB2_2:
    pop.w   r4
    ret

이제 기술적으로 이러한 솔루션 중 일부에 성능 차이가 있습니다. 때로는 결과가 5 개 경우 결과가 6 개 코드를 넘어 가고 그 반대의 경우 분기가 실행하는 것보다 빠릅니다. 논쟁 할 수는 있지만 실행은 다양해야합니다 그러나 그것은 컴파일러가 if this jump over else 실행을 수행하는 결과로 코드에서 if 조건 대 if 조건에 더 가깝습니다. 그러나 이것은 반드시 코딩 스타일 때문이 아니라 어떤 구문의 비교와 if 및 else 케이스 때문입니다.


좋아, 어셈블리는 태그 중 하나이므로 코드가 의사 코드라고 가정하고 (반드시 c는 아님) 사람이 6502 어셈블리로 변환합니다.

첫 번째 옵션 (다른 옵션 없음)

        ldy #$00
        lda #$05
        dey
        bmi false
        lda #$06
false   brk

두 번째 옵션 (다른 옵션 포함)

        ldy #$00
        dey
        bmi else
        lda #$06
        sec
        bcs end
else    lda #$05
end     brk

Assumptions: Condition is in Y register set this to 0 or 1 on the first line of either option, result will be in accumulator.

So, after counting cycles for both possibilities of each case, we see that the 1st construct is generally faster; 9 cycles when condition is 0 and 10 cycles when condition is 1, whereas option two is also 9 cycles when condition is 0, but 13 cycles when condition is 1. (cycle counts do not include the BRK at the end).

Conclusion: If only is faster than If-Else construct.

And for completeness, here is an optimized value = condition + 5 solution:

ldy #$00
lda #$00
tya
adc #$05
brk

This cuts our time down to 8 cycles (again not including the BRK at the end).

참고URL : https://stackoverflow.com/questions/43202012/if-statement-vs-if-else-statement-which-is-faster

반응형