Entrambi i loop sono infiniti, ma possiamo vedere quale richiede più istruzioni / risorse per iterazione.
Usando gcc, ho compilato i due seguenti programmi da assemblare a vari livelli di ottimizzazione:
int main(void) {
while(1) {}
return 0;
}
int main(void) {
while(2) {}
return 0;
}
Anche senza ottimizzazioni ( -O0
), l'assembly generato era identico per entrambi i programmi .Pertanto, non vi è alcuna differenza di velocità tra i due loop.
Per riferimento, ecco l'assieme generato (usando gcc main.c -S -masm=intel
con un flag di ottimizzazione):
Con -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"
Con -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"
Con -O2
e -O3
(stesso output):
.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"
In effetti, l'assemblaggio generato per il loop è identico per ogni livello di ottimizzazione:
.L2:
jmp .L2
.seh_endproc
.ident "GCC: (tdm64-2) 4.8.1"
I bit importanti sono:
.L2:
jmp .L2
Non riesco a leggere molto bene l'assemblaggio, ma questo è ovviamente un ciclo incondizionato. L' jmp
istruzione reimposta incondizionatamente il programma .L2
sull'etichetta senza nemmeno confrontare un valore con vero, e ovviamente lo fa immediatamente di nuovo fino a quando il programma non è in qualche modo terminato. Ciò corrisponde direttamente al codice C / C ++:
L2:
goto L2;
Modificare:
È interessante notare che, anche senza ottimizzazioni , i loop seguenti hanno prodotto tutti lo stesso output esatto (incondizionato jmp
) nell'assembly:
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 anche con mio stupore:
#include<math.h>
while(sqrt(7)) {}
while(hypot(3,4)) {}
Le cose diventano un po 'più interessanti con le funzioni definite dall'utente:
int x(void) {
return 1;
}
while(x()) {}
#include<math.h>
double x(void) {
return sqrt(7);
}
while(x()) {}
A -O0
, questi due esempi in realtà chiamano x
ed eseguono un confronto per ogni iterazione.
Primo esempio (ritorno 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"
Secondo esempio (ritorno 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"
Tuttavia, in -O1
e sopra, entrambi producono lo stesso assieme degli esempi precedenti (un jmp
ritorno incondizionato all'etichetta precedente).
TL; DR
Sotto GCC, i diversi loop vengono compilati in assembly identici. Il compilatore valuta i valori costanti e non si preoccupa di eseguire alcun confronto effettivo.
La morale della storia è:
- Esiste un livello di traduzione tra il codice sorgente C ++ e le istruzioni della CPU e questo livello ha importanti implicazioni per le prestazioni.
- Pertanto, le prestazioni non possono essere valutate solo guardando il codice sorgente.
- Il compilatore dovrebbe essere abbastanza intelligente da ottimizzare casi così banali. I programmatori non dovrebbero perdere tempo a pensarci nella stragrande maggioranza dei casi.