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;
condition
0 또는 1 이라고 가정합니다 . 더 높은 최적화 수준은 처음에 레지스터를 movzx
효율적으로 제로화 하여이를 방지하는 것을 제외하고는 실제로 출력을 변경하지 않습니다 EAX
.
면책 조항 : 당신 의 의도가 당신의 코드를 읽는 사람들에게 즉각적으로 분명하지 않을 수 있기 때문에 ( 정수 유형으로의 5 + condition
변환이 true
를 제공 한다고 표준이 보장하더라도) 당신은 당신 자신을 작성해서는 안됩니다 1
. 이 코드의 요점은 컴파일러가 두 경우 모두에서 생성하는 것이 (실질적으로) 동일하다는 것을 보여주는 것입니다. Ciprian Tomoiaga 는 의견에서이를 아주 잘 설명합니다.
인간 의 작업은 코드를 작성하는 것입니다 인간에 대한 그리고하자 컴파일러 에 대한 코드를 작성 기계를 .
에서 대답 CompuChip에 대한 쇼는 int
그들이 모두 같은 어셈블리에 최적화 된, 그래서 그것은 중요하지 않습니다.
값이 행렬이면 어떨까요?
나는 이것을 좀 더 일반적인 방식으로 해석 할 것입니다. 즉 value
, 구조와 할당이 비싸고 이동이 저렴한 유형 이라면 어떨까요?
그때
T value = init1;
if (condition)
value = init2;
condition
true 인 경우 불필요한 초기화를 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를 않습니다 당신이 이런 종류의 일이 일어날 수 있다는 것을 인식 할 필요가 있으므로 아웃 오브 오더 실행의 "가장 잘 알려진 구현"을 사용은 자주 사용 지침에 잘라 모서리 가능성이있다. 실제 예는 의 대상 레지스터에 잘못된 데이터 종속성
popcnt
과lzcnt
샌디 브릿지 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
'developer tip' 카테고리의 다른 글
센터 (0) | 2020.10.06 |
---|---|
필수 속성 HTML5 (0) | 2020.10.06 |
MySQL 비밀번호가 만료되었습니다. (0) | 2020.10.06 |
+ 연산자가 C에서 구현되는 방식입니까? (0) | 2020.10.06 |
현재 날짜로부터 30 일 전에받는 방법은 무엇입니까? (0) | 2020.10.06 |