두 루프는 모두 무한하지만 반복마다 더 많은 명령어 / 리소스가 필요한 루프를 확인할 수 있습니다.
gcc를 사용하여 다음 두 프로그램을 다양한 수준의 최적화로 어셈블리로 컴파일했습니다.
int main(void) {
while(1) {}
return 0;
}
int main(void) {
while(2) {}
return 0;
}
도없이 최적화 (함께 -O0
), 생성 된 조립체를 모두 프로그램 동일 하였다 . 따라서 두 루프 사이에 속도 차이가 없습니다.
참고로, gcc main.c -S -masm=intel
최적화 플래그와 함께 생성 된 어셈블리는 다음 과 같습니다.
로 -O0
:
.file "main.c"
.intel_syntax noprefix
.def __main; .scl 2; .type 32; .endef
.text
.globl main
.def main; .scl 2; .type 32; .endef
.seh_proc main
main:
push rbp
.seh_pushreg rbp
mov rbp, rsp
.seh_setframe rbp, 0
sub rsp, 32
.seh_stackalloc 32
.seh_endprologue
call __main
.L2:
jmp .L2
.seh_endproc
.ident "GCC: (tdm64-2) 4.8.1"
로 -O1
:
.file "main.c"
.intel_syntax noprefix
.def __main; .scl 2; .type 32; .endef
.text
.globl main
.def main; .scl 2; .type 32; .endef
.seh_proc main
main:
sub rsp, 40
.seh_stackalloc 40
.seh_endprologue
call __main
.L2:
jmp .L2
.seh_endproc
.ident "GCC: (tdm64-2) 4.8.1"
부착 -O2
하고 -O3
(동일한 출력) :
.file "main.c"
.intel_syntax noprefix
.def __main; .scl 2; .type 32; .endef
.section .text.startup,"x"
.p2align 4,,15
.globl main
.def main; .scl 2; .type 32; .endef
.seh_proc main
main:
sub rsp, 40
.seh_stackalloc 40
.seh_endprologue
call __main
.L2:
jmp .L2
.seh_endproc
.ident "GCC: (tdm64-2) 4.8.1"
실제로 루프에 대해 생성 된 어셈블리는 모든 최적화 수준에서 동일합니다.
.L2:
jmp .L2
.seh_endproc
.ident "GCC: (tdm64-2) 4.8.1"
중요한 부분은 다음과 같습니다.
.L2:
jmp .L2
어셈블리를 잘 읽을 수는 없지만 무조건 루프입니다. 이 jmp
명령 .L2
은 값을 true와 비교하지 않고도 프로그램을 무조건 레이블로 다시 재설정하며 , 프로그램이 종료 될 때까지 즉시 다시 수행합니다. 이것은 C / C ++ 코드와 직접 일치합니다.
L2:
goto L2;
편집하다:
흥미롭게 도 최적화 가 없는 경우에도 다음 루프 jmp
는 어셈블리에서 정확히 동일한 출력 (무조건 )을 생성했습니다 .
while(42) {}
while(1==1) {}
while(2==2) {}
while(4<7) {}
while(3==3 && 4==4) {}
while(8-9 < 0) {}
while(4.3 * 3e4 >= 2 << 6) {}
while(-0.1 + 02) {}
그리고 심지어 놀랍게도 :
#include<math.h>
while(sqrt(7)) {}
while(hypot(3,4)) {}
사용자 정의 함수는 조금 더 흥미로워집니다.
int x(void) {
return 1;
}
while(x()) {}
#include<math.h>
double x(void) {
return sqrt(7);
}
while(x()) {}
에서 -O0
,이 두 가지 예는 실제로 호출 x
하고 각 반복에 대한 비교를 수행합니다.
첫 번째 예 (1을 반환) :
.L4:
call x
testl %eax, %eax
jne .L4
movl $0, %eax
addq $32, %rsp
popq %rbp
ret
.seh_endproc
.ident "GCC: (tdm64-2) 4.8.1"
두 번째 예 ( sqrt(7)
) :
.L4:
call x
xorpd %xmm1, %xmm1
ucomisd %xmm1, %xmm0
jp .L4
xorpd %xmm1, %xmm1
ucomisd %xmm1, %xmm0
jne .L4
movl $0, %eax
addq $32, %rsp
popq %rbp
ret
.seh_endproc
.ident "GCC: (tdm64-2) 4.8.1"
그러나 -O1
둘 이상에서 이전 예제와 동일한 어셈블리를 생성합니다 ( jmp
이전 레이블로 돌아가는 무조건 ).
TL; DR
GCC에서 다른 루프는 동일한 어셈블리로 컴파일됩니다. 컴파일러는 상수 값을 평가하고 실제 비교를 수행하지 않습니다.
이야기의 교훈은 다음과 같습니다.
- C ++ 소스 코드와 CPU 명령어간에 변환 계층이 있으며이 계층은 성능에 중요한 영향을 미칩니다.
- 따라서 소스 코드 만보고 성능을 평가할 수 없습니다.
- 컴파일러 는 그러한 사소한 경우를 최적화하기에 충분히 똑똑 해야합니다 . 프로그래머 는 대부분의 경우에 대해 생각하면서 시간을 낭비 해서는 안됩니다 .