दोनों लूप अनंत हैं, लेकिन हम देख सकते हैं कि कौन सा प्रति पुनरावृत्ति अधिक निर्देश / संसाधन लेता है।
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
पूर्ववर्ती लेबल पर बिना शर्त वापस)।
टी एल; डॉ
जीसीसी के तहत, अलग-अलग छोरों को समान विधानसभा के लिए संकलित किया जाता है। कंपाइलर निरंतर मूल्यों का मूल्यांकन करता है और किसी भी वास्तविक तुलना के प्रदर्शन को परेशान नहीं करता है।
कहानी का नैतिक है:
- सी ++ स्रोत कोड और सीपीयू निर्देशों के बीच अनुवाद की एक परत मौजूद है, और इस परत के प्रदर्शन के लिए महत्वपूर्ण निहितार्थ हैं।
- इसलिए, प्रदर्शन का मूल्यांकन केवल स्रोत कोड को देखकर नहीं किया जा सकता है।
- कंपाइलर को ऐसे तुच्छ मामलों को अनुकूलित करने के लिए पर्याप्त स्मार्ट होना चाहिए । अधिकांश मामलों में प्रोग्रामर्स को उनके बारे में सोचने में अपना समय बर्बाद नहीं करना चाहिए ।