Les deux boucles sont infinies, mais nous pouvons voir laquelle prend plus d'instructions / ressources par itération.
En utilisant gcc, j'ai compilé les deux programmes suivants pour l'assemblage à différents niveaux d'optimisation:
int main(void) {
while(1) {}
return 0;
}
int main(void) {
while(2) {}
return 0;
}
Même sans optimisations ( -O0
), l'assembly généré était identique pour les deux programmes . Par conséquent, il n'y a pas de différence de vitesse entre les deux boucles.
Pour référence, voici l'assembly généré (à l'aide gcc main.c -S -masm=intel
d'un indicateur d'optimisation):
Avec -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"
Avec -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"
Avec -O2
et -O3
(même sortie):
.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"
En effet, l'assembly généré pour la boucle est identique pour chaque niveau d'optimisation:
.L2:
jmp .L2
.seh_endproc
.ident "GCC: (tdm64-2) 4.8.1"
Les bits importants étant:
.L2:
jmp .L2
Je ne peux pas très bien lire l'assemblage, mais c'est évidemment une boucle inconditionnelle. L' jmp
instruction réinitialise inconditionnellement le programme sur l' .L2
étiquette sans même comparer une valeur par rapport à true, et bien sûr le fait immédiatement à nouveau jusqu'à ce que le programme soit en quelque sorte terminé. Cela correspond directement au code C / C ++:
L2:
goto L2;
Éditer:
Chose intéressante, même sans optimisation , les boucles suivantes ont toutes produit exactement la même sortie (inconditionnelle jmp
) dans l'assemblage:
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) {}
Et même à mon grand étonnement:
#include<math.h>
while(sqrt(7)) {}
while(hypot(3,4)) {}
Les choses deviennent un peu plus intéressantes avec les fonctions définies par l'utilisateur:
int x(void) {
return 1;
}
while(x()) {}
#include<math.h>
double x(void) {
return sqrt(7);
}
while(x()) {}
À -O0
, ces deux exemples appellent x
et effectuent une comparaison pour chaque itération.
Premier exemple (retour 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"
Deuxième exemple (retour 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"
Cependant, au niveau -O1
et au -dessus, ils produisent tous les deux le même assemblage que les exemples précédents (un jmp
retour inconditionnel à l'étiquette précédente).
TL; DR
Sous GCC, les différentes boucles sont compilées en assemblage identique. Le compilateur évalue les valeurs constantes et ne prend pas la peine d'effectuer une comparaison réelle.
La morale de l'histoire est:
- Il existe une couche de traduction entre le code source C ++ et les instructions CPU, et cette couche a des implications importantes pour les performances.
- Par conséquent, les performances ne peuvent pas être évaluées en regardant uniquement le code source.
- Le compilateur doit être suffisamment intelligent pour optimiser ces cas triviaux. Les programmeurs ne devraient pas perdre leur temps à y penser dans la grande majorité des cas.