Her iki döngü de sonsuzdur, ancak hangisinin yineleme başına daha fazla talimat / kaynak aldığını görebiliriz.
Gcc'yi kullanarak, aşağıdaki iki programı çeşitli optimizasyon düzeylerinde birleştirmek için derledim:
int main(void) {
while(1) {}
return 0;
}
int main(void) {
while(2) {}
return 0;
}
Hiçbir optimizasyon ( -O0
) yapılmasa bile , oluşturulan montaj her iki program için de aynıydı . Bu nedenle, iki döngü arasında hız farkı yoktur.
Başvuru için, burada oluşturulan derleme ( gcc main.c -S -masm=intel
bir optimizasyon bayrağıyla kullanarak ):
İle -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"
İle -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"
İle -O2
ve -O3
(aynı çıkış):
.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"
Aslında, döngü için oluşturulan montaj her optimizasyon seviyesi için aynıdır:
.L2:
jmp .L2
.seh_endproc
.ident "GCC: (tdm64-2) 4.8.1"
Önemli bitler:
.L2:
jmp .L2
Montajı çok iyi okuyamıyorum, ama bu açıkça koşulsuz bir döngü. jmp
Talimat koşulsuz program geri sıfırlar .L2
bile doğru karşı bir değeri karşılaştırarak olmadan etiket ve program nasılsa sona kadar tabii hemen bu nedenle yeniden yapar. Bu doğrudan C / C ++ koduna karşılık gelir:
L2:
goto L2;
Düzenle:
İlginçtir ki, hiçbir optimizasyon olmasa bile , aşağıdaki döngülerin hepsi jmp
montajda aynı çıktıyı (koşulsuz ) üretti :
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) {}
Ve hayretlerime bile:
#include<math.h>
while(sqrt(7)) {}
while(hypot(3,4)) {}
Kullanıcı tanımlı işlevlerle işler biraz daha ilginçleşir:
int x(void) {
return 1;
}
while(x()) {}
#include<math.h>
double x(void) {
return sqrt(7);
}
while(x()) {}
At -O0
, bu iki örnek aslında x
her yineleme için bir karşılaştırma çağırır ve gerçekleştirir.
İlk örnek (1 döndürme):
.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"
İkinci örnek (geri dönüyor 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"
Bununla birlikte, -O1
her ikisinde de ve her ikisi de önceki örneklerle aynı montajı üretir ( jmp
önceki etikete koşulsuz bir geri dönüş).
TL; DR
GCC altında, farklı döngüler özdeş montaj için derlenir. Derleyici sabit değerleri değerlendirir ve gerçek bir karşılaştırma yapmakla uğraşmaz.
Hikayenin ahlakı:
- C ++ kaynak kodu ve CPU talimatları arasında bir çeviri katmanı vardır ve bu katmanın performans için önemli etkileri vardır.
- Bu nedenle, performans yalnızca kaynak koduna bakarak değerlendirilemez.
- Derleyici bu tür önemsiz durumları optimize edecek kadar akıllı olmalıdır . Programcılar olmamalıdır vakaların büyük çoğunluğunda onlar hakkında onların zaman düşünme israf.