Beide Schleifen sind unendlich, aber wir können sehen, welche mehr Anweisungen / Ressourcen pro Iteration benötigt.
Mit gcc habe ich die beiden folgenden Programme für die Montage auf verschiedenen Optimierungsstufen kompiliert:
int main(void) {
while(1) {}
return 0;
}
int main(void) {
while(2) {}
return 0;
}
Auch ohne Optimierungen ( -O0
) war die generierte Assembly für beide Programme identisch . Daher gibt es keinen Geschwindigkeitsunterschied zwischen den beiden Schleifen.
Als Referenz ist hier die generierte Assembly (unter Verwendung gcc main.c -S -masm=intel
eines Optimierungsflags):
Mit -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"
Mit -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"
Mit -O2
und -O3
(gleiche Ausgabe):
.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"
Tatsächlich ist die für die Schleife generierte Baugruppe für jede Optimierungsstufe identisch:
.L2:
jmp .L2
.seh_endproc
.ident "GCC: (tdm64-2) 4.8.1"
Die wichtigen Punkte sind:
.L2:
jmp .L2
Ich kann Assembly nicht sehr gut lesen, aber dies ist offensichtlich eine bedingungslose Schleife. Die jmp
Anweisung setzt das Programm bedingungslos auf das .L2
Label zurück, ohne einen Wert mit true zu vergleichen, und tut dies natürlich sofort wieder, bis das Programm irgendwie beendet ist. Dies entspricht direkt dem C / C ++ - Code:
L2:
goto L2;
Bearbeiten:
Interessanterweise erzeugten die folgenden Schleifen auch ohne Optimierungenjmp
in der Montage genau dieselbe Ausgabe (bedingungslos ):
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) {}
Und sogar zu meinem Erstaunen:
#include<math.h>
while(sqrt(7)) {}
while(hypot(3,4)) {}
Mit benutzerdefinierten Funktionen wird es etwas interessanter:
int x(void) {
return 1;
}
while(x()) {}
#include<math.h>
double x(void) {
return sqrt(7);
}
while(x()) {}
Bei -O0
diesen beiden Beispielen wird tatsächlich x
ein Vergleich für jede Iteration aufgerufen und durchgeführt.
Erstes Beispiel (Rückgabe 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"
Zweites Beispiel (Rückkehr 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"
Bei -O1
und darüber produzieren beide jedoch dieselbe Baugruppe wie die vorherigen Beispiele (eine bedingungslose jmp
Rückkehr zum vorhergehenden Etikett).
TL; DR
Unter GCC werden die verschiedenen Schleifen zu identischen Baugruppen kompiliert. Der Compiler wertet die konstanten Werte aus und führt keinen tatsächlichen Vergleich durch.
Die Moral der Geschichte lautet:
- Es gibt eine Übersetzungsebene zwischen C ++ - Quellcode und CPU-Anweisungen, und diese Ebene hat wichtige Auswirkungen auf die Leistung.
- Daher kann die Leistung nicht nur anhand des Quellcodes bewertet werden.
- Der Compiler sollte intelligent genug sein, um solche trivialen Fälle zu optimieren. Programmierer sollten in den allermeisten Fällen nicht ihre Zeit damit verschwenden, über sie nachzudenken.