ลูปทั้งสองนั้นไม่มีที่สิ้นสุด แต่เราสามารถเห็นได้ว่าอันไหนใช้คำสั่ง / ทรัพยากรเพิ่มเติมต่อการวนซ้ำ
ด้วยการใช้ gcc ฉันได้รวบรวมสองโปรแกรมต่อไปนี้เพื่อประกอบในระดับการปรับให้เหมาะสมต่าง ๆ :
int main(void) {
while(1) {}
return 0;
}
int main(void) {
while(2) {}
return 0;
}
ถึงแม้ไม่มีการเพิ่มประสิทธิภาพ ( -O0
) ที่สร้างขึ้นประกอบเป็นเหมือนกันสำหรับทั้งสองโปรแกรม ดังนั้นจึงไม่มีความแตกต่างของความเร็วระหว่างสองลูป
สำหรับการอ้างอิงต่อไปนี้เป็นชุดประกอบที่สร้างขึ้น (ใช้gcc main.c -S -masm=intel
กับแฟล็กการปรับให้เหมาะสม):
ด้วย-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"
ด้วย-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"
ด้วย-O2
และ-O3
(เอาต์พุตเดียวกัน):
.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"
ในความเป็นจริงชุดประกอบที่สร้างขึ้นสำหรับลูปจะเหมือนกันสำหรับการเพิ่มประสิทธิภาพทุกระดับ:
.L2:
jmp .L2
.seh_endproc
.ident "GCC: (tdm64-2) 4.8.1"
บิตที่สำคัญคือ:
.L2:
jmp .L2
ฉันไม่สามารถอ่านการชุมนุมได้ดี แต่เห็นได้ชัดว่านี่เป็นการวนซ้ำแบบไม่มีเงื่อนไข jmp
การเรียนการสอนโดยไม่มีเงื่อนไขรีเซ็ตโปรแกรมกลับไปที่.L2
ป้ายโดยไม่ได้เปรียบเทียบกับมูลค่าที่แท้จริงและแน่นอนทันทีไม่เช่นนั้นอีกครั้งจนกว่าโปรแกรมจะสิ้นสุดอย่างใด ตรงนี้สอดคล้องกับรหัส C / C ++:
L2:
goto L2;
แก้ไข:
น่าสนใจพอถึงแม้จะไม่มีการเพิ่มประสิทธิภาพแต่ลูปต่อไปนี้ทั้งหมดสร้างเอาต์พุตเดียวกัน (โดยไม่มีเงื่อนไขjmp
) ที่แน่นอนในชุดประกอบ:
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) {}
และถึงความประหลาดใจของฉัน:
#include<math.h>
while(sqrt(7)) {}
while(hypot(3,4)) {}
สิ่งต่าง ๆ ที่น่าสนใจยิ่งขึ้นด้วยฟังก์ชั่นที่ผู้ใช้กำหนด:
int x(void) {
return 1;
}
while(x()) {}
#include<math.h>
double x(void) {
return sqrt(7);
}
while(x()) {}
ที่-O0
ตัวอย่างสองตัวอย่างนี้โทรx
และทำการเปรียบเทียบสำหรับการวนซ้ำแต่ละครั้ง
ตัวอย่างแรก (ส่งคืน 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"
ตัวอย่างที่สอง (กลับมา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"
อย่างไรก็ตามที่-O1
และข้างบนพวกเขาทั้งสองผลิตแอสเซมบลีเดียวกันกับตัวอย่างก่อนหน้า ( jmp
กลับไม่มีเงื่อนไขกลับไปที่ป้ายชื่อก่อนหน้า)
TL; DR
ภายใต้ GCC ลูปต่าง ๆ จะถูกคอมไพล์ไปยังแอสเซมบลีที่เหมือนกัน คอมไพเลอร์ประเมินค่าคงที่และไม่รบกวนการเปรียบเทียบจริงใด ๆ
คุณธรรมของเรื่องราวคือ:
- มีเลเยอร์การแปลอยู่ระหว่างซอร์สโค้ด C ++ และคำสั่ง CPU และเลเยอร์นี้มีนัยสำคัญต่อประสิทธิภาพ
- ดังนั้นประสิทธิภาพไม่สามารถประเมินได้โดยดูจากซอร์สโค้ดเท่านั้น
- คอมไพเลอร์ควรฉลาดพอที่จะเพิ่มประสิทธิภาพกรณีเล็ก ๆ น้อย ๆ โปรแกรมเมอร์ไม่ควรเสียเวลาคิดถึงพวกเขาในกรณีส่วนใหญ่