है if( a < 901 )
तेजी से if( a <= 900 )
।
इस सरल उदाहरण में बिल्कुल नहीं, लेकिन लूप कॉम्प्लेक्स कोड पर मामूली प्रदर्शन परिवर्तन हैं। मुझे लगता है कि यह उत्पन्न मशीन कोड के साथ कुछ करना है अगर यह सच है।
<
की तुलना में टाइपिंग दो गुना तेज है <=
।
है if( a < 901 )
तेजी से if( a <= 900 )
।
इस सरल उदाहरण में बिल्कुल नहीं, लेकिन लूप कॉम्प्लेक्स कोड पर मामूली प्रदर्शन परिवर्तन हैं। मुझे लगता है कि यह उत्पन्न मशीन कोड के साथ कुछ करना है अगर यह सच है।
<
की तुलना में टाइपिंग दो गुना तेज है <=
।
जवाबों:
नहीं, यह अधिकांश आर्किटेक्चर पर तेज़ नहीं होगा। आपने निर्दिष्ट नहीं किया, लेकिन x86 पर, सभी अभिन्न तुलनाओं को आमतौर पर दो मशीन निर्देशों में लागू किया जाएगा:
test
या cmp
निर्देश, जो सेट करता हैEFLAGS
Jcc
(छलांग) निर्देश :
jne
- नहीं के बराबर कूदें -> ZF = 0
jz
- शून्य (बराबर) -> कूदें ZF = 1
jg
- अधिक होने पर कूदें -> ZF = 0 and SF = OF
उदाहरण (संक्षिप्तता के लिए संपादित) के साथ संकलित$ gcc -m32 -S -masm=intel test.c
if (a < b) {
// Do something 1
}
इसके संकलन:
mov eax, DWORD PTR [esp+24] ; a
cmp eax, DWORD PTR [esp+28] ; b
jge .L2 ; jump if a is >= b
; Do something 1
.L2:
तथा
if (a <= b) {
// Do something 2
}
इसके संकलन:
mov eax, DWORD PTR [esp+24] ; a
cmp eax, DWORD PTR [esp+28] ; b
jg .L5 ; jump if a is > b
; Do something 2
.L5:
इसलिए दोनों के बीच एकमात्र अंतर jg
बनाम एक jge
निर्देश है। दोनों को समान समय लगेगा।
मैं टिप्पणी को संबोधित करना चाहता हूं कि कुछ भी इंगित नहीं करता है कि विभिन्न कूद निर्देश समय की एक ही राशि लेते हैं। यह जवाब देने के लिए थोड़ा मुश्किल है, लेकिन यहां मैं क्या दे सकता हूं: इंटेल इंस्ट्रक्शन सेट संदर्भ में , वे सभी एक सामान्य निर्देश के तहत एक साथ समूहीकृत हैं, Jcc
(कूदो यदि स्थिति पूरी हो गई है)। एक ही समूहन को परिशिष्ट संदर्भ नियमावली के तहत परिशिष्ट C. लेटेंसी और थ्रूपुट में एक साथ किया जाता है।
विलंबता - निष्पादन कोर के लिए आवश्यक सभी घड़ी चक्रों की संख्या जो एक निर्देश बनाने वाले सभी ofops के निष्पादन को पूरा करने के लिए आवश्यक हैं।
थ्रूपुट - इश्यू पोर्ट से पहले प्रतीक्षा करने के लिए आवश्यक घड़ी चक्रों की संख्या फिर से उसी निर्देश को स्वीकार करने के लिए स्वतंत्र है। कई निर्देशों के लिए, एक निर्देश का प्रवाह अपने विलंबता से काफी कम हो सकता है
इसके लिए मान Jcc
निम्न हैं:
Latency Throughput
Jcc N/A 0.5
निम्नलिखित फुटनोट के साथ Jcc
:
7) सशर्त कूद निर्देशों का चयन शाखाओं की भविष्यवाणी की स्थिति में सुधार करने के लिए अनुभाग 3.4.1, “शाखा भविष्यवाणी अनुकूलन” की सिफारिश पर आधारित होना चाहिए। जब शाखाओं की सफलतापूर्वक भविष्यवाणी की जाती है, तो इसका विलंबता
jcc
प्रभावी रूप से शून्य होता है।
इसलिए, इंटेल डॉक्स में कुछ भी कभी Jcc
भी एक निर्देश को दूसरों से अलग नहीं मानता है।
यदि कोई निर्देशों को लागू करने के लिए उपयोग किए जाने वाले वास्तविक सर्किटरी के बारे में सोचता है, तो यह मान सकता है कि EFLAGS
शर्तों को पूरा करने के लिए अलग-अलग बिट्स पर सरल और / या द्वार होंगे । फिर, कोई कारण नहीं है कि दो बिट्स के परीक्षण का एक निर्देश केवल एक परीक्षण की तुलना में अधिक या कम समय लेना चाहिए (गेट प्रचार विलंब में देरी, जो घड़ी की अवधि की तुलना में बहुत कम है।)
संपादित करें: फ्लोटिंग पॉइंट
यह x87 फ्लोटिंग पॉइंट के लिए भी सही है: (ऊपर की तरह बहुत ही कोड, लेकिन double
इसके बजाय int
।)
fld QWORD PTR [esp+32]
fld QWORD PTR [esp+40]
fucomip st, st(1) ; Compare ST(0) and ST(1), and set CF, PF, ZF in EFLAGS
fstp st(0)
seta al ; Set al if above (CF=0 and ZF=0).
test al, al
je .L2
; Do something 1
.L2:
fld QWORD PTR [esp+32]
fld QWORD PTR [esp+40]
fucomip st, st(1) ; (same thing as above)
fstp st(0)
setae al ; Set al if above or equal (CF=0).
test al, al
je .L5
; Do something 2
.L5:
leave
ret
jg
और jnle
एक ही निर्देश हैं, 7F
:-)
ऐतिहासिक रूप से (हम 1980 के दशक और 1990 के दशक की शुरुआत में बात कर रहे थे), कुछ वास्तुशिल्प थे जिनमें यह सच था। मूल मुद्दा यह है कि पूर्णांक तुलना अंतर्निहित पूर्णांक के माध्यम से स्वाभाविक रूप से कार्यान्वित की जाती है। यह निम्नलिखित मामलों को जन्म देता है।
Comparison Subtraction
---------- -----------
A < B --> A - B < 0
A = B --> A - B = 0
A > B --> A - B > 0
अब, जब A < B
घटाव को घटाव के लिए उच्च-बिट को सही होने के लिए उधार लेना पड़ता है, जैसे आप ले जाते हैं और हाथ से जोड़ते और घटाते समय उधार लेते हैं। इस "उधार" बिट को आमतौर पर कैरी बिट के रूप में संदर्भित किया जाता था और एक शाखा निर्देश द्वारा परीक्षण किया जाएगा। शून्य बिट नामक एक दूसरा बिट सेट किया जाएगा यदि घटाव पहचान शून्य था जो समानता को निहित करता है।
आमतौर पर कम से कम दो सशर्त शाखा निर्देश होते थे, एक कैरी बिट पर शाखा और दूसरा शून्य बिट पर।
अब, मामले के दिल में लाने के लिए, ले जाने और शून्य बिट परिणामों को शामिल करने के लिए पिछली तालिका का विस्तार करें।
Comparison Subtraction Carry Bit Zero Bit
---------- ----------- --------- --------
A < B --> A - B < 0 0 0
A = B --> A - B = 0 1 1
A > B --> A - B > 0 1 0
तो, के लिए एक शाखा को लागू करना A < B
एक निर्देश में किया जा सकता है, क्योंकि कैरी बिट केवल इस मामले में स्पष्ट है, अर्थात्।
;; Implementation of "if (A < B) goto address;"
cmp A, B ;; compare A to B
bcz address ;; Branch if Carry is Zero to the new address
लेकिन, अगर हम कम-से-कम या बराबर-बराबर तुलना करना चाहते हैं, तो हमें समानता के मामले को पकड़ने के लिए शून्य ध्वज की अतिरिक्त जांच करने की आवश्यकता है।
;; Implementation of "if (A <= B) goto address;"
cmp A, B ;; compare A to B
bcz address ;; branch if A < B
bzs address ;; also, Branch if the Zero bit is Set
इसलिए, कुछ मशीनों पर, "कम से कम" तुलना का उपयोग करने से एक मशीन निर्देश को बचाया जा सकता है । यह सब-मेगाहर्ट्ज़ प्रोसेसर गति और 1: 1 सीपीयू-टू-मेमोरी स्पीड अनुपात के युग में प्रासंगिक था, लेकिन यह आज लगभग पूरी तरह अप्रासंगिक है।
jge
, जो शून्य और साइन / फ्लैग दोनों का परीक्षण करते हैं।
<=
परीक्षण को एक निर्देश में लागू किया जा सकता है जिसमें ऑपरेंड्स की अदला-बदली और परीक्षण not <
(इसके बराबर >=
) यह <=
स्वैप किए गए ऑपरेंड के साथ वांछित है cmp B,A; bcs addr
:। यही कारण है कि यह परीक्षण इंटेल द्वारा छोड़ा गया था, उन्होंने इसे बेमानी माना और आप उस समय निरर्थक निर्देशों को बर्दाश्त नहीं कर सके :-)
यह मानते हुए कि हम आंतरिक पूर्णांक प्रकारों के बारे में बात कर रहे हैं, कोई भी संभव तरीका नहीं है जो एक दूसरे से तेज हो सकता है। वे स्पष्ट रूप से शब्दार्थ समान हैं। वे दोनों कंपाइलर से एक ही काम करने के लिए कहते हैं। केवल एक बुरी तरह से टूटे हुए कंपाइलर इनमें से किसी एक के लिए अवर कोड उत्पन्न करेंगे।
अगर वहाँ कुछ मंच कहाँ था <
तेजी से था <=
सरल पूर्णांक प्रकार के लिए, संकलक चाहिए हमेशा कन्वर्ट <=
करने के लिए <
स्थिरांक के लिए। कोई भी कंपाइलर जो सिर्फ एक बुरा कंपाइलर (उस प्लेटफॉर्म के लिए) नहीं होगा।
<
है और न ही <=
जब तक संकलक का फैसला करता है जो गति वे होगा गति है। यह संकलक के लिए एक बहुत ही सरल अनुकूलन है जब आप विचार करते हैं कि वे आम तौर पर पहले से ही डेड कोड ऑप्टिमाइज़ेशन, टेल कॉल ऑप्टिमाइज़ेशन, लूप होस्टिंग (और अवसरों पर अनियंत्रित), विभिन्न लूपों का स्वचालित समांतरिकरण आदि करते हैं ... क्यों समय बर्बाद करना समय से पहले अनुकूलन करना ? एक प्रोटोटाइप चलाएं, यह निर्धारित करने के लिए प्रोफ़ाइल करें कि सबसे महत्वपूर्ण ऑप्टिमाइज़ेशन कहां हैं, महत्व के क्रम में उन ऑप्टिमाइज़ेशन का प्रदर्शन करें और प्रगति को मापने के तरीके के साथ फिर से प्रोफ़ाइल करें ...
(a < C)
करने के लिए (a <= C-1)
(कुछ निरंतर के लिए C
) का कारण बनता है C
अनुदेश सेट में एन्कोड करने के लिए और अधिक कठिन हो सकता है। उदाहरण के लिए, एक निर्देश सेट तुलना में एक कॉम्पैक्ट रूप में हस्ताक्षरित स्थिरांक -127 से 128 तक का प्रतिनिधित्व करने में सक्षम हो सकता है, लेकिन उस सीमा के बाहर स्थिरांक को एक लंबी, धीमी एन्कोडिंग या पूरी तरह से एक अन्य निर्देश का उपयोग करके लोड करना पड़ता है। तो तुलना की तरह (a < -127)
एक सीधा परिवर्तन नहीं हो सकता है।
a > 127
a > 128
a > 127
a >= 128
<=
करने के लिए <
स्थिरांक के लिए"। जहाँ तक मुझे पता है, उस परिवर्तन में निरंतर परिवर्तन शामिल है। उदाहरण के लिए, a <= 42
संकलित किया गया है a < 43
क्योंकि <
तेज है। कुछ किनारे के मामलों में, इस तरह का परिवर्तन फलदायी नहीं होगा क्योंकि नए निरंतर को अधिक या धीमी निर्देशों की आवश्यकता हो सकती है। बेशक a > 127
और a >= 128
समतुल्य हैं और एक संकलक को दोनों रूपों को (एक ही) सबसे तेज़ तरीके से सांकेतिक शब्दों में बदलना चाहिए, लेकिन मैंने जो कहा है, उसके साथ यह असंगत नहीं है।
मैं देखता हूं कि न तो तेज है। कंपाइलर प्रत्येक मशीन में एक ही मशीन कोड अलग मूल्य के साथ उत्पन्न करता है।
if(a < 901)
cmpl $900, -4(%rbp)
jg .L2
if(a <=901)
cmpl $901, -4(%rbp)
jg .L3
मेरा उदाहरण if
लिनक्स पर G86 से x86_64 प्लेटफॉर्म पर है।
कंपाइलर लेखक बहुत स्मार्ट लोग हैं, और वे इन चीजों के बारे में सोचते हैं और कई अन्य जिन्हें हम में से अधिकांश लोग मानते हैं।
मैंने देखा कि यदि यह स्थिर नहीं है, तो एक ही मशीन कोड किसी भी स्थिति में उत्पन्न होता है।
int b;
if(a < b)
cmpl -4(%rbp), %eax
jge .L2
if(a <=b)
cmpl -4(%rbp), %eax
jg .L3
if(a <=900)
यह दिखाने के लिए उपयोग करना चाहिए कि यह वास्तव में समान asm उत्पन्न करता है :)
फ्लोटिंग पॉइंट कोड के लिए, आधुनिक आर्किटेक्चर पर <= तुलना वास्तव में धीमी (एक निर्देश द्वारा) हो सकती है। यहाँ पहला कार्य है:
int compare_strict(double a, double b) { return a < b; }
पावरपीसी पर, पहले यह एक फ्लोटिंग पॉइंट तुलना (जो अपडेट करता है cr
, कंडीशन रजिस्टर) करता है, फिर हालत रजिस्टर को एक जीपीआर में ले जाता है, "बिट की तुलना में कम" जगह में स्थानांतरित करता है, और फिर लौटता है। यह चार निर्देश लेता है।
अब इस फ़ंक्शन पर विचार करें:
int compare_loose(double a, double b) { return a <= b; }
इसके लिए compare_strict
ऊपर के समान कार्य की आवश्यकता है , लेकिन अब इसमें दो बिट्स हैं: "से कम" और "के बराबर था।" इसके लिए cror
इन दोनों बिट्स को एक में मिलाने के लिए एक अतिरिक्त निर्देश ( - कंडीशन रजिस्टर बिटवाइज़ OR) की आवश्यकता होती है। इसलिए compare_loose
पांच निर्देशों की आवश्यकता है, जबकि compare_strict
चार की आवश्यकता है।
आप सोच सकते हैं कि कंपाइलर दूसरे फ़ंक्शन को पसंद कर सकता है:
int compare_loose(double a, double b) { return ! (a > b); }
हालांकि यह गलत तरीके से NaN को हैंडल करेगा। NaN1 <= NaN2
और NaN1 > NaN2
दोनों को असत्य का मूल्यांकन करने की आवश्यकता है।
fucomip
ZF और CF सेट करता है।
cr
है की तरह झंडे के बराबर ZF
और CF
86 पर। (हालांकि सीआर अधिक लचीला है।) जिस पोस्टर के बारे में बात की जा रही है वह परिणाम को एक जीपीआर में ले जा रहा है: जो पावरपीसी पर दो निर्देश लेता है, लेकिन x86 में एक सशर्त चाल निर्देश है।
हो सकता है कि उस अनाम पुस्तक के लेखक ने पढ़ा हो जो a > 0
तेजी से चलता है a >= 1
और सोचता है कि यह सार्वभौमिक रूप से सच है।
लेकिन ऐसा इसलिए है क्योंकि 0
इसमें शामिल है (क्योंकि CMP
, आर्किटेक्चर के आधार पर, उदाहरण के साथ प्रतिस्थापित किया जा सकता है OR
) और इसकी वजह से नहीं <
।
(a >= 1)
धीमी चलाने के लिए एक खराब संकलक लगेगा (a > 0)
, क्योंकि पूर्व को तुच्छ रूप से ऑप्टिमाइज़र द्वारा उत्तरार्द्ध में बदल दिया जा सकता है ..
बहुत कम से कम, अगर यह सच होता तो एक कंपाइलर एक <= b से (a> b) को तुच्छ रूप से ऑप्टिमाइज़ कर सकता था, और इसलिए भले ही तुलना वास्तव में धीमी हो, लेकिन सभी भोले कंपाइलर के साथ आपको अंतर नजर नहीं आएगा! ।
NOT
सिर्फ अन्य निर्देश ( je
बनाम jne
) से बना है
उनकी एक ही गति है। हो सकता है कि कुछ विशेष वास्तुकला में उसने जो कहा, वह सही हो, लेकिन x86 परिवार में कम से कम मुझे पता है कि वे समान हैं। क्योंकि ऐसा करने के लिए सीपीयू एक विकल्प (ए - बी) करेगा और फिर ध्वज रजिस्टर के झंडे की जांच करेगा। उस रजिस्टर के दो बिट्स को ZF (जीरो फ्लैग) और SF (साइन फ्लैग) कहा जाता है, और यह एक चक्र में किया जाता है, क्योंकि यह एक मास्क ऑपरेशन के साथ करेगा।
यह अंतर्निहित आर्किटेक्चर पर अत्यधिक निर्भर होगा जो C को संकलित किया गया है। कुछ प्रोसेसर और आर्किटेक्चर के पास अलग-अलग संख्या में चक्रों के निष्पादन के बराबर या उससे कम या उसके बराबर के लिए स्पष्ट निर्देश हो सकते हैं।
हालांकि यह बहुत असामान्य होगा, क्योंकि कंपाइलर इसके चारों ओर काम कर सकता है, जिससे यह अप्रासंगिक हो जाएगा।
वास्तुकला, संकलक और भाषा के अधिकांश संयोजनों के लिए यह जल्दी नहीं होगा।
अन्य उत्तर पर ध्यान केंद्रित किया है x86 वास्तुकला, और मैं नहीं जानता कि एआरएम वास्तुकला (अपने उदाहरण कोडांतरक हो रहा है जो) अच्छी तरह से तैयार किए गए कोड पर विशेष रूप से टिप्पणी करने के लिए पर्याप्त है, लेकिन इस एक का एक उदाहरण है सूक्ष्म अनुकूलन जो है बहुत वास्तुकला विशिष्ट, और एक विरोधी अनुकूलन होने की संभावना है क्योंकि यह एक अनुकूलन होना है ।
जैसे, मैं सुझाव दूंगा कि इस प्रकार का माइक्रो-ऑप्टिमाइज़ेशन सर्वश्रेष्ठ सॉफ्टवेयर इंजीनियरिंग अभ्यास के बजाय कार्गो पंथ प्रोग्रामिंग का एक उदाहरण है ।
संभवतः कुछ आर्किटेक्चर हैं जहां यह एक अनुकूलन है, लेकिन मुझे कम से कम एक आर्किटेक्चर का पता है जहां विपरीत सच हो सकता है। सम्मानित Transputer वास्तुकला के लिए ही मशीन कोड निर्देश था के बराबर और से बड़ा या बराबर है, इसलिए सभी तुलना इन पुरातन से बनाया जाना था।
फिर भी, लगभग सभी मामलों में, कंपाइलर मूल्यांकन निर्देशों को इस तरह से आदेश दे सकता है कि व्यवहार में, किसी भी अन्य पर किसी भी तुलना का कोई लाभ नहीं था। सबसे खराब स्थिति हालांकि, ऑपरेंड स्टैक पर शीर्ष दो वस्तुओं को स्वैप करने के लिए रिवर्स इंस्ट्रक्शन (आरईवी) जोड़ने की आवश्यकता हो सकती है । यह एक एकल बाइट निर्देश था जिसे चलाने के लिए एक एकल चक्र लिया गया था, इसलिए सबसे छोटा ओवरहेड संभव था।
इस तरह का माइक्रो-ऑप्टिमाइज़ेशन ऑप्टिमाइज़ेशन है या नहीं, एंटी-ऑप्टिमाइज़ेशन आपके द्वारा उपयोग किए जा रहे विशिष्ट आर्किटेक्चर पर निर्भर करता है, इसलिए आमतौर पर आर्किटेक्चर विशिष्ट माइक्रो-ऑप्टिमाइज़ेशन का उपयोग करने की आदत में शामिल होना एक बुरा विचार है, अन्यथा आप सहज रूप से हो सकते हैं ऐसा करने के लिए अनुचित होने पर एक का उपयोग करें, और ऐसा दिखता है कि आप जो किताब पढ़ रहे हैं वह ठीक उसी तरह है।
यदि कोई हो, तो भी आपको अंतर पर ध्यान नहीं देना चाहिए। इसके अलावा, व्यवहार में, आपको कुछ अतिरिक्त करना होगा a + 1
या a - 1
जब तक आप कुछ जादुई स्थिरांक का उपयोग नहीं करने जा रहे हैं, तब तक खड़े रहना होगा, जो हर तरह से बहुत बुरा अभ्यास है।
आप कह सकते हैं कि अधिकांश स्क्रिप्टिंग भाषाओं में लाइन सही है, क्योंकि अतिरिक्त चरित्र के परिणामस्वरूप थोड़ा धीमा कोड प्रसंस्करण होता है। हालाँकि, जैसा कि शीर्ष उत्तर ने कहा है, इसका C ++ में कोई प्रभाव नहीं होना चाहिए, और स्क्रिप्टिंग भाषा के साथ किया जा रहा कुछ भी ऐसा नहीं है जो अनुकूलन के लिए चिंतित है।
जब मैंने यह उत्तर लिखा था, मैं केवल सामान्य रूप में <बनाम <= के बारे में शीर्षक प्रश्न देख रहा था, न कि एक स्थिर a < 901
बनाम का विशिष्ट उदाहरण a <= 900
। कई कंपाइलर हमेशा <
और के बीच परिवर्तित करके स्थिरांक की भयावहता को कम करते हैं <=
, जैसे कि x86 तत्काल ऑपरेंड में -128 के लिए एक छोटा बाइट एन्कोडिंग होता है ।.127।
एआरएम और विशेष रूप से AArch64 के लिए, एक तत्काल के रूप में सांकेतिक शब्दों में बदलना एक संकीर्ण क्षेत्र को एक शब्द में किसी भी स्थिति में घुमाने में सक्षम होने पर निर्भर करता है। अतः cmp w0, #0x00f000
अतिक्रमण होगा, जबकि cmp w0, #0x00effff
नहीं हो सकता है। इसलिए तुलनात्मक संकलन के लिए मेक-इट-छोटा नियम हमेशा AArch64 के लिए लागू नहीं होता है।
अधिकांश मशीनों पर असेंबली भाषा में, एक तुलना के लिए एक <=
ही लागत है <
। यह उस पर लागू होता है चाहे आप उस पर शाखा लगा रहे हों, इसे 0/1 पूर्णांक बनाने के लिए बूलियनाइज़ कर रहे हों, या इसे एक शाखा रहित चयन ऑपरेशन (जैसे x86 CMOV) के लिए विधेय के रूप में उपयोग कर रहे हों। अन्य उत्तरों ने केवल प्रश्न के इस भाग को संबोधित किया है।
लेकिन यह सवाल C ++ ऑपरेटरों, अनुकूलक के इनपुट के बारे में है । आम तौर पर वे दोनों समान रूप से कुशल होते हैं; पुस्तक से सलाह पूरी तरह से फर्जी लगती है क्योंकि कंपाइलर हमेशा उस तुलना को बदल सकते हैं जिसे वे एसम में लागू करते हैं। लेकिन कम से कम एक अपवाद है जहां <=
संयोगवश कुछ ऐसा बना सकते हैं जो संकलक अनुकूलन नहीं कर सकता है।
लूप स्थिति के रूप में, ऐसे मामले होते हैं <=
जो गुणात्मक रूप से भिन्न होते हैं <
, जब यह संकलक को यह साबित करने से रोकता है कि लूप अनंत नहीं है। यह एक बड़ा अंतर बना सकता है, ऑटो-वेक्टरकरण को अक्षम करना।
हस्ताक्षरित ओवरफ्लो (यूबी) के विपरीत, बिना सौंपा ओवरफ्लो को बेस -2 रैप के रूप में अच्छी तरह से परिभाषित किया गया है। हस्ताक्षर किए गए लूप काउंटर आमतौर पर संकलक के साथ सुरक्षित होते हैं जो हस्ताक्षरित-अतिप्रवाह यूबी के आधार पर अनुकूलन करते हैं जो नहीं हो रहा है: ++i <= size
हमेशा अंततः गलत हो जाएगा। ( हर सी प्रोग्रामर को अपरिभाषित व्यवहार के बारे में क्या जानना चाहिए )
void foo(unsigned size) {
unsigned upper_bound = size - 1; // or any calculation that could produce UINT_MAX
for(unsigned i=0 ; i <= upper_bound ; i++)
...
कंपाइलर केवल उन तरीकों को अनुकूलित कर सकते हैं जो सभी संभावित इनपुट मानों के लिए C ++ स्रोत के (परिभाषित और कानूनी रूप से अवलोकन योग्य) व्यवहार को संरक्षित करते हैं , सिवाय उन लोगों के जो अपरिभाषित व्यवहार का नेतृत्व करते हैं।
(एक साधारण i <= size
भी समस्या पैदा करेगा, लेकिन मैंने सोचा कि ऊपरी सीमा की गणना गलती से इनपुट के लिए एक अनन्त लूप की संभावना का परिचय देने का एक अधिक यथार्थवादी उदाहरण है, जिसके बारे में आपको परवाह नहीं है लेकिन जिसे संकलक को विचार करना चाहिए।)
इस मामले में, size=0
की ओर जाता है upper_bound=UINT_MAX
, और i <= UINT_MAX
हमेशा सच है। तो यह लूप के लिए अनंत है size=0
, और कंपाइलर को सम्मान देना होगा कि भले ही आप प्रोग्रामर के रूप में कभी भी आकार = 0 पास करने का इरादा न करें। यदि कंपाइलर इस फ़ंक्शन को एक कॉलर में इनलाइन कर सकता है, जहां यह साबित कर सकता है कि आकार = 0 असंभव है, तो बहुत अच्छा, यह ऑप्टिमाइज़ कर सकता है जैसे यह कर सकता है i < size
।
जैसे लूप if(!size) skip the loop;
do{...}while(--size);
ऑप्टिमाइज़ करने का एक सामान्य तरीका है for( i<size )
, अगर लूप के i
अंदर वास्तविक मूल्य की आवश्यकता नहीं है ( क्यों लूप हमेशा "डू ... कॉम्प्लेक्स" (टेल जंप)? ) में संकलित किए जाते हैं ।
लेकिन वह {} जबकि अनंत नहीं हो सकता है: अगर साथ में प्रवेश किया है size==0
, तो हमें 2 ^ n पुनरावृत्तियों मिलते हैं। ( लूप C के लिए सभी अहस्ताक्षरित पूर्णांक से अधिक का उपयोग करना) शून्य सहित सभी अहस्ताक्षरित पूर्णांकों पर एक लूप व्यक्त करना संभव बनाता है, लेकिन जिस तरह से यह asm में है, उसे कैरी फ़्लैग के बिना आसान नहीं है।)
लूप काउंटर के रैपराउंड के साथ एक संभावना होने के नाते, आधुनिक कंपाइलर अक्सर बस "छोड़ देते हैं", और लगभग आक्रामक रूप से अनुकूलन नहीं करते हैं।
अहस्ताक्षरित i <= n
पराजित क्लैंग के मुहावरे-मान्यता का उपयोग करना जोsum(1 .. n)
गॉस के n * (n+1) / 2
फार्मूले के आधार पर एक बंद फॉर्म के साथ लूप को अनुकूलित करता है।
unsigned sum_1_to_n_finite(unsigned n) {
unsigned total = 0;
for (unsigned i = 0 ; i < n+1 ; ++i)
total += i;
return total;
}
Godbolt संकलक एक्सप्लोरर पर xang7.0 और gcc8.2 से x86-64 asm
# clang7.0 -O3 closed-form
cmp edi, -1 # n passed in EDI: x86-64 System V calling convention
je .LBB1_1 # if (n == UINT_MAX) return 0; // C++ loop runs 0 times
# else fall through into the closed-form calc
mov ecx, edi # zero-extend n into RCX
lea eax, [rdi - 1] # n-1
imul rax, rcx # n * (n-1) # 64-bit
shr rax # n * (n-1) / 2
add eax, edi # n + (stuff / 2) = n * (n+1) / 2 # truncated to 32-bit
ret # computed without possible overflow of the product before right shifting
.LBB1_1:
xor eax, eax
ret
लेकिन भोले संस्करण के लिए, हमें बस क्लैंग से एक गूंगा लूप मिलता है।
unsigned sum_1_to_n_naive(unsigned n) {
unsigned total = 0;
for (unsigned i = 0 ; i<=n ; ++i)
total += i;
return total;
}
# clang7.0 -O3
sum_1_to_n(unsigned int):
xor ecx, ecx # i = 0
xor eax, eax # retval = 0
.LBB0_1: # do {
add eax, ecx # retval += i
add ecx, 1 # ++1
cmp ecx, edi
jbe .LBB0_1 # } while( i<n );
ret
जीसीसी किसी भी तरह से एक बंद-फॉर्म का उपयोग नहीं करता है, इसलिए लूप स्थिति का विकल्प वास्तव में इसे चोट नहीं पहुंचाता है ; यह SIMD पूर्णांक जोड़ के साथ ऑटो-वेक्टर करता है, जो i
एक एक्सएमएम रजिस्टर के तत्वों में समानांतर में 4 मान चलाता है ।
# "naive" inner loop
.L3:
add eax, 1 # do {
paddd xmm0, xmm1 # vect_total_4.6, vect_vec_iv_.5
paddd xmm1, xmm2 # vect_vec_iv_.5, tmp114
cmp edx, eax # bnd.1, ivtmp.14 # bound and induction-variable tmp, I think.
ja .L3 #, # }while( n > i )
"finite" inner loop
# before the loop:
# xmm0 = 0 = totals
# xmm1 = {0,1,2,3} = i
# xmm2 = set1_epi32(4)
.L13: # do {
add eax, 1 # i++
paddd xmm0, xmm1 # total[0..3] += i[0..3]
paddd xmm1, xmm2 # i[0..3] += 4
cmp eax, edx
jne .L13 # }while( i != upper_limit );
then horizontal sum xmm0
and peeled cleanup for the last n%3 iterations, or something.
इसमें एक प्लेन स्केलर लूप भी है जो मुझे लगता है कि यह बहुत छोटे n
और / या अनंत लूप केस के लिए उपयोग करता है ।
BTW, इन दोनों छोरों लूप ओवरहेड पर एक निर्देश (और सैंडब्रिज-परिवार सीपीयू पर एक यूओपी) बर्बाद करते हैं। sub eax,1
/ cmp / jcc के jnz
बजाय add eax,1
अधिक कुशल होगा। 2 के बजाय 1 यूओपी (उप / jcc या cmp / jcc के मैक्रो-फ्यूजन के बाद)। दोनों लूप के बाद का कोड EAX बिना शर्त लिखता है, इसलिए यह लूप काउंटर के अंतिम मूल्य का उपयोग नहीं कर रहा है।
<
या के लिए नहीं <=
। लेकिन यकीन है कि, test ecx,ecx
/ bt eax, 3
/ jbe
यदि जेडएफ, (ECX == 0) सेट कर दिया जाता है या यदि सीएफ सेट किया गया है (थोड़ा EAX के 3 == 1) पर चला जाएगा, सबसे CPUs पर एक आंशिक झंडा स्टाल के कारण क्योंकि झंडे इसे पढ़ता है सब नहीं है किसी भी झंडे को लिखने के लिए अंतिम निर्देश से आते हैं। सैंडीब्रिज-परिवार में, यह वास्तव में स्टाल नहीं है, बस एक मर्जिंग यूओपी डालने की जरूरत है। cmp
/ test
सभी झंडे लिखें, लेकिन bt
ZF को असंबद्ध छोड़ देता है। felixcloutier.com/x86/bt
केवल अगर कंप्यूटर बनाने वाले लोग बूलियन लॉजिक के साथ खराब हैं। जो उन्हें नहीं होना चाहिए।
हर तुलना ( >=
<=
>
<
) एक ही गति से की जा सकती है।
हर तुलना जो है, वह केवल एक घटाव (अंतर) है और यह देखने पर कि यह सकारात्मक / नकारात्मक है।
(यदि msb
सेट किया गया है, तो संख्या ऋणात्मक है)
कैसे करें जांच a >= b
? सब पॉजिटिव होने पर a-b >= 0
चेक करें a-b
।
कैसे करें जांच a <= b
? सब पॉजिटिव होने पर 0 <= b-a
चेक करें b-a
।
कैसे करें जांच a < b
? a-b < 0
यदि a-b
नकारात्मक है तो सब चेक करें ।
कैसे करें जांच a > b
? 0 > b-a
यदि b-a
नकारात्मक है तो सब चेक करें ।
सीधे शब्दों में कहें, कंप्यूटर बस दिए गए ऑप के लिए हुड के नीचे ऐसा कर सकता है:
a >= b
== msb(a-b)==0
a <= b
== msb(b-a)==0
a > b
== msb(b-a)==1
a < b
==msb(a-b)==1
और निश्चित रूप से कंप्यूटर को वास्तव में ==0
या ==1
तो करने की आवश्यकता नहीं होगी ।
के लिए ==0
यह सिर्फ msb
सर्किट से पलटना कर सकता है ।
वैसे भी, वे निश्चित a >= b
रूप से a>b || a==b
योग्य के रूप में गणना नहीं की गई होगी