Ambos os loops são infinitos, mas podemos ver qual deles leva mais instruções / recursos por iteração.
Usando o gcc, compilei os dois programas a seguir para montagem em vários níveis de otimização:
int main(void) {
while(1) {}
return 0;
}
int main(void) {
while(2) {}
return 0;
}
Mesmo sem otimizações ( -O0
), o assembly gerado era idêntico para os dois programas . Portanto, não há diferença de velocidade entre os dois loops.
Para referência, aqui está o assembly gerado (usando gcc main.c -S -masm=intel
com um sinalizador de otimização):
Com -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"
Com -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"
Com -O2
e -O3
(mesma saída):
.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"
De fato, a montagem gerada para o loop é idêntica para todos os níveis de otimização:
.L2:
jmp .L2
.seh_endproc
.ident "GCC: (tdm64-2) 4.8.1"
Os bits importantes são:
.L2:
jmp .L2
Não consigo ler a montagem muito bem, mas esse é obviamente um loop incondicional. A jmp
instrução redefine incondicionalmente o programa de volta ao .L2
rótulo, sem mesmo comparar um valor com true, e, é claro, imediatamente o faz novamente até que o programa seja encerrado de alguma forma. Isso corresponde diretamente ao código C / C ++:
L2:
goto L2;
Editar:
Curiosamente, mesmo sem otimizações , todos os seguintes loops produziram exatamente a mesma saída (incondicional jmp
) na montagem:
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) {}
E até para minha surpresa:
#include<math.h>
while(sqrt(7)) {}
while(hypot(3,4)) {}
As coisas ficam um pouco mais interessantes com as funções definidas pelo usuário:
int x(void) {
return 1;
}
while(x()) {}
#include<math.h>
double x(void) {
return sqrt(7);
}
while(x()) {}
Na -O0
, esses dois exemplos realmente chamam x
e executam uma comparação para cada iteração.
Primeiro exemplo (retornando 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"
Segundo exemplo (retornando 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"
No entanto, -O1
acima e acima, ambos produzem o mesmo conjunto que os exemplos anteriores (um jmp
retorno incondicional ao rótulo anterior).
TL; DR
Sob o GCC, os diferentes loops são compilados para montagem idêntica. O compilador avalia os valores constantes e não se incomoda em realizar nenhuma comparação real.
A moral da história é:
- Existe uma camada de tradução entre o código fonte C ++ e as instruções da CPU, e essa camada tem implicações importantes para o desempenho.
- Portanto, o desempenho não pode ser avaliado olhando apenas o código-fonte.
- O compilador deve ser inteligente o suficiente para otimizar esses casos triviais. Os programadores não devem perder tempo pensando neles na grande maioria dos casos.