Kedua loop tidak terbatas, tetapi kita dapat melihat mana yang membutuhkan lebih banyak instruksi / sumber daya per iterasi.
Dengan menggunakan gcc, saya mengkompilasi dua program berikut untuk perakitan pada berbagai tingkat optimasi:
int main(void) {
while(1) {}
return 0;
}
int main(void) {
while(2) {}
return 0;
}
Bahkan tanpa optimasi ( -O0
), perakitan yang dihasilkan identik untuk kedua program . Oleh karena itu, tidak ada perbedaan kecepatan antara kedua loop.
Untuk referensi, berikut adalah rakitan yang dihasilkan (menggunakan gcc main.c -S -masm=intel
dengan bendera optimisasi):
Dengan -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"
Dengan -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"
Dengan -O2
dan -O3
(keluaran yang sama):
.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"
Bahkan, rakitan yang dihasilkan untuk loop identik untuk setiap tingkat optimasi:
.L2:
jmp .L2
.seh_endproc
.ident "GCC: (tdm64-2) 4.8.1"
Bit-bit penting adalah:
.L2:
jmp .L2
Saya tidak bisa membaca perakitan dengan baik, tetapi ini jelas merupakan loop tanpa syarat. The jmp
instruksi tanpa syarat me-reset program kembali ke .L2
label tanpa membandingkan nilai terhadap yang benar, dan tentu saja segera melakukannya lagi sampai program ini entah bagaimana berakhir. Ini berhubungan langsung dengan kode C / C ++:
L2:
goto L2;
Edit:
Cukup menarik, bahkan tanpa optimasi , loop berikut semuanya menghasilkan output yang sama persis (tanpa syarat jmp
) dalam perakitan:
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) {}
Dan bahkan keheranan saya:
#include<math.h>
while(sqrt(7)) {}
while(hypot(3,4)) {}
Hal-hal menjadi sedikit lebih menarik dengan fungsi yang ditentukan pengguna:
int x(void) {
return 1;
}
while(x()) {}
#include<math.h>
double x(void) {
return sqrt(7);
}
while(x()) {}
Pada -O0
, dua contoh ini benar-benar memanggil x
dan melakukan perbandingan untuk setiap iterasi.
Contoh pertama (pengembalian 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"
Contoh kedua (kembali 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"
Namun, pada -O1
dan di atas, mereka berdua menghasilkan rakitan yang sama seperti contoh sebelumnya ( jmp
kembali tanpa syarat ke label sebelumnya).
TL; DR
Di bawah GCC, loop yang berbeda dikompilasi ke rakitan yang sama. Kompiler mengevaluasi nilai-nilai konstan dan tidak repot melakukan perbandingan yang sebenarnya.
Moral dari cerita ini adalah:
- Ada lapisan terjemahan antara kode sumber C ++ dan instruksi CPU, dan lapisan ini memiliki implikasi penting untuk kinerja.
- Oleh karena itu, kinerja tidak dapat dievaluasi hanya dengan melihat kode sumber.
- Kompiler harus cukup pintar untuk mengoptimalkan kasus sepele seperti itu. Pemrogram tidak boleh membuang waktu untuk memikirkan mereka dalam sebagian besar kasus.