क्या यह गिनती करने के लिए तेजी से गिनना है?


131

हमारे कंप्यूटर विज्ञान शिक्षक ने एक बार कहा था कि किसी कारण से यह गिनती करने की तुलना में अधिक कुशल है। उदाहरण के लिए यदि आपको एक लूप का उपयोग करने की आवश्यकता है और लूप इंडेक्स का कहीं उपयोग नहीं किया जाता है (जैसे स्क्रीन पर N * की एक पंक्ति को प्रिंट करना) मेरा मतलब है कि कोड इस तरह है:

for (i = N; i >= 0; i--)  
  putchar('*');  

से बेहतर है:

for (i = 0; i < N; i++)  
  putchar('*');  

क्या यह वास्तव में सच है? और यदि हां, तो क्या कोई जानता है कि क्यों?


6
कौन सा कंप्यूटर वैज्ञानिक? किस प्रकाशन में?
बामरगुलिस

26
यह बोधगम्य है कि आप एक नैनोसेकंड प्रति पुनरावृत्ति को बचा सकते हैं, या ऊनी मैमथ के परिवार पर एक बाल के रूप में ज्यादा। putcharसमय (दे या ले) की 99.9999% का उपयोग कर रहा है।
माइक डनलैवी

38
सभी बुराईयो की जड़ समयपूर्व इष्टतमीकरण है। जो भी फ़ॉर्म आपको सही लगता है उसका उपयोग करें, क्योंकि (जैसा कि आप पहले से ही जानते हैं) वे तार्किक रूप से समकक्ष हैं। प्रोग्रामिंग का सबसे कठिन हिस्सा कार्यक्रम के सिद्धांत को अन्य प्रोग्रामर (और स्वयं!) से संवाद कर रहा है। एक निर्माण का उपयोग करना जो आपको या कुछ अन्य प्रोग्रामर को कभी भी एक दूसरे से अधिक के लिए देखता है, शुद्ध नुकसान है। आप कभी भी उस समय को याद नहीं करेंगे जब कोई यह सोचता है कि "यह क्यों गिना जाता है?"
डेविड एम

61
पहला लूप स्पष्ट रूप से धीमा है, क्योंकि यह 11 बार पुचर कहता है, जबकि दूसरा केवल 10 बार कॉल करता है।
पॉल कुलीनेविक्ज़

17
क्या आपने देखा कि यदि iअहस्ताक्षरित है, तो पहला लूप अनंत है?
शाहबाज

जवाबों:


371

क्या यह वास्तव में सच है? और यदि कोई ऐसा जानता है तो क्यों?

प्राचीन दिनों में, जब कंप्यूटर अभी भी हाथ से जुड़े सिलिका से बाहर निकलते थे, जब 8-बिट माइक्रोकंट्रोलर पृथ्वी पर घूमते थे, और जब आपका शिक्षक युवा था (या आपके शिक्षक का शिक्षक युवा था), वहाँ एक सामान्य मशीन निर्देश था जिसे अपघटन और छोड़ना कहा जाता था। यदि शून्य (DSZ)। लोट्स को लागू करने के लिए हॉटशॉट विधानसभा प्रोग्रामर्स ने इस निर्देश का उपयोग किया। बाद में मशीनों को कट्टर निर्देश मिले, लेकिन अभी भी काफी कुछ प्रोसेसर थे, जिस पर शून्य की तुलना में कुछ और के साथ तुलना करना सस्ता था। (यह कुछ आधुनिक RISC मशीनों पर भी सच है, जैसे PPC या SPARC, जो एक पूरे रजिस्टर को हमेशा शून्य रखने के लिए आरक्षित करते हैं।)

इसलिए, यदि आप अपने छोरों को शून्य के साथ तुलना करने के लिए रिग करते हैं, तो Nक्या हो सकता है?

  • आप एक रजिस्टर बचा सकते हैं
  • आपको एक छोटे बाइनरी एन्कोडिंग के साथ तुलना निर्देश मिल सकता है
  • यदि कोई पिछला निर्देश एक ध्वज सेट करने के लिए होता है (केवल x86 परिवार मशीनों पर), तो आपको स्पष्ट तुलना निर्देश की आवश्यकता भी नहीं हो सकती है

क्या ये अंतर आधुनिक आउट-ऑफ-ऑर्डर प्रोसेसर पर वास्तविक कार्यक्रमों पर किसी भी औसत दर्जे के सुधार के परिणामस्वरूप हो सकते हैं ? लगभग नामुमकिन। वास्तव में, मैं प्रभावित होऊंगा यदि आप एक माइक्रोबेनमार्क पर भी एक औसत दर्जे का सुधार दिखा सकते हैं।

सारांश: मैं आपके शिक्षक को सिर पर उल्टा लटका देता हूं! आपको लूप को व्यवस्थित करने के बारे में अप्रचलित छद्म तथ्यों को सीखना नहीं चाहिए। आपको सीखना चाहिए कि छोरों के बारे में सबसे महत्वपूर्ण बात यह सुनिश्चित करना है कि वे समाप्त कर दें , सही उत्तर दें , और पढ़ने में आसान हों मैं चाहता हूं कि आपका शिक्षक महत्वपूर्ण सामग्री पर ध्यान केंद्रित करे न कि पौराणिक कथाओं पर।


3
++ और इसके अलावा, putcharलूप ओवरहेड की तुलना में लंबे समय तक परिमाण के कई आदेश लेता है।
माइक डनलैवी

41
यह कड़ाई से पौराणिक कथा नहीं है: यदि वह किसी प्रकार के uber- अनुकूलित वास्तविक समय प्रणाली कर रहा है, तो यह काम में आएगा। लेकिन हैकर का वह प्रकार शायद यह सब पहले से ही जानता होगा और निश्चित रूप से प्रवेश स्तर के सीएस छात्रों को आर्काना के साथ भ्रमित नहीं करेगा।
पॉल नाथन

4
@ जोशुआ: किस तरह से इस अनुकूलन का पता लगाया जा सकेगा? जैसा कि प्रश्नकर्ता ने कहा, लूप इंडेक्स का उपयोग लूप में ही नहीं किया जाता है, बशर्ते कि पुनरावृत्तियों की संख्या समान हो और व्यवहार में कोई बदलाव न हो। शुद्धता के प्रमाण के संदर्भ में, चर प्रतिस्थापन बनाने से j=N-iपता चलता है कि दोनों छोर समान हैं।
psmears

7
सारांश के लिए +1। आधुनिक हार्डवेयर पर यह पसीना नहीं है क्योंकि यह लगभग कोई फर्क नहीं पड़ता। यह लगभग 20 साल पहले कोई फर्क नहीं पड़ा। यदि आपको लगता है कि आपको देखभाल करनी है, तो इसे दोनों तरीके से करें, कोई स्पष्ट अंतर नहीं देखें, और कोड को स्पष्ट और सही ढंग से लिखने के लिए वापस जाएं
डोनाल्ड फेलो

3
मुझे नहीं पता कि मुझे शरीर के लिए अपवोट करना चाहिए या सारांश के लिए डाउनवोट होना चाहिए।
डेन्यूबियन नाविक

29

यहाँ कुछ हार्डवेयर पर क्या हो सकता है यह इस बात पर निर्भर करता है कि संकलक आपके द्वारा उपयोग किए जा रहे नंबरों की सीमा के बारे में क्या घटा सकता है: वृद्धिशील लूप के साथ आपको i<Nहर बार लूप के दौर का परीक्षण करना होगा । घटते संस्करण के लिए, कैरी फ्लैग (घटाव के साइड इफेक्ट के रूप में सेट) स्वचालित रूप से आपको बता सकता है कि क्याi>=0 । यह लूप राउंड प्रति बार एक परीक्षण बचाता है।

वास्तव में, आधुनिक पाइपलाइन प्रोसेसर हार्डवेयर पर, यह सामान लगभग निश्चित रूप से अप्रासंगिक है क्योंकि निर्देशों से लेकर घड़ी चक्र तक एक सरल 1-1 मैपिंग नहीं है। (हालांकि मैं इसकी कल्पना कर सकता हूं कि अगर आप माइक्रोकंट्रोलर से ठीक समय पर वीडियो सिग्नल पैदा करने जैसी चीजें कर रहे थे। लेकिन तब आप असेंबली भाषा में लिखेंगे।)


2
वह शून्य ध्वज नहीं होगा और न ही ध्वज होगा?
बॉब

2
@ थोबड़ा इस मामले में आप शून्य तक पहुँचना चाहते हैं, एक परिणाम प्रिंट कर सकते हैं, आगे की गिरावट, और फिर पाते हैं कि आप शून्य से नीचे ले गए हैं (या उधार)। लेकिन थोड़ा अलग ढंग से लिखा गया एक घटता हुआ लूप इसके बजाय शून्य ध्वज का उपयोग कर सकता है।
सिगफैप

1
बस पूरी तरह से पांडित्यपूर्ण होने के लिए, सभी आधुनिक हार्डवेयर पाइपलाइन नहीं हैं। एंबेडेड प्रोसेसर इस तरह के microoptimization के लिए और अधिक प्रासंगिकता होगी।
पॉल नाथन

@Paul जैसा कि मुझे Atmel AVR के साथ कुछ अनुभव है, मैं माइक्रोकंट्रोलर्स का उल्लेख करना नहीं भूली ...
sigfpe

27

Intel x86 इंस्ट्रक्शन सेट में, लूप को शून्य तक गिनने के लिए निर्माण आमतौर पर लूप की तुलना में कम निर्देशों के साथ किया जा सकता है जो एक गैर-शून्य निकास स्थिति तक गिना जाता है। विशेष रूप से, ECX रजिस्टर पारंपरिक रूप से x86 asm में लूप काउंटर के रूप में उपयोग किया जाता है, और Intel निर्देश सेट में एक विशेष jcxz जंप निर्देश है जो परीक्षण के परिणाम के आधार पर शून्य और जंप के लिए ECX रजिस्टर का परीक्षण करता है।

हालाँकि, प्रदर्शन अंतर तब तक नगण्य होगा जब तक कि आपका लूप घड़ी चक्र की गणना के लिए पहले से ही संवेदनशील न हो। शून्य तक की गिनती गिनती की तुलना में लूप के प्रत्येक पुनरावृत्ति से 4 या 5 घड़ी चक्र को दाढ़ी कर सकती है, इसलिए यह वास्तव में एक उपयोगी तकनीक की तुलना में एक नवीनता से अधिक है।

इसके अलावा, इन दिनों एक अच्छा अनुकूलन करने वाला कंपाइलर लूप सोर्स कोड को काउंट डाउन को शून्य मशीन कोड में बदलने में सक्षम होना चाहिए (यह निर्भर करता है कि आप लूप इंडेक्स चर का उपयोग कैसे करते हैं) इसलिए वास्तव में आपके लूप लिखने का कोई कारण नहीं है अजीब तरीके सिर्फ एक चक्र या दो को इधर-उधर निचोड़ने के लिए।


2
मैंने माइक्रोसॉफ्ट के C ++ कंपाइलर को कुछ साल पहले से देखा है। यह देखने में सक्षम है कि लूप इंडेक्स का उपयोग नहीं किया गया है, इसलिए यह इसे सबसे तेज़ रूप में पुन: व्यवस्थित करता है।
मार्क रैनसम

1
@Mark: डेल्फी संकलक के साथ-साथ, 1996 में शुरू
dthorpe

4
@MarkRansom वास्तव में, संकलक लूप इंडेक्स चर का उपयोग करने के बावजूद लूप को कैसे उपयोग किया जाता है, इसके आधार पर लूप को लागू करने में सक्षम हो सकता है। यदि लूप इंडेक्स वैरिएबल का उपयोग केवल स्थिर सरणियों (कंपाइल टाइम में ज्ञात आकार के सरणियों) में इंडेक्स करने के लिए किया जाता है, तो ऐरे इंडेक्सिंग को ptr + array size - loop index var के रूप में किया जा सकता है, जो अभी भी x86 में एकल निर्देश हो सकता है। यह कोडांतरक डिबगिंग होना और लूप को नीचे गिनते हुए देखना पसंद करता है लेकिन सरणी सूचकांकों को ऊपर जा रहा है!
डोरटेप

1
वास्तव में आज आपके कंपाइलर शायद लूप और jecxz निर्देशों का उपयोग नहीं करेंगे क्योंकि वे एक dec / jnz जोड़ी की तुलना में धीमी हैं।
12

1
@FUZxxl अपने पाश को अजीब तरीके से नहीं लिखने के लिए सभी और अधिक कारण। मानव पठनीय स्पष्ट कोड लिखें और संकलक को अपना काम करने दें।
dthorpe

23

हाँ..!!

N से 0 तक की गिनती थोड़ी तेज़ है कि 0 से N की गणना इस मायने में की जाती है कि हार्डवेयर कैसे तुलना करेगा।

प्रत्येक लूप में तुलना पर ध्यान दें

i>=0
i<N

अधिकांश प्रोसेसर में शून्य निर्देश के साथ तुलना होती है..तो पहले वाले को मशीन कोड में अनुवादित किया जाएगा:

  1. लोड i
  2. तुलना करें और कूदें यदि कम या बराबर शून्य

लेकिन दूसरे को हर बार एन फॉर्म मेमोरी लोड करने की आवश्यकता होती है

  1. लोड करें
  2. भार N
  3. उप i और N
  4. तुलना करें और कूदें यदि कम या बराबर शून्य

तो यह नीचे या ऊपर की गिनती के कारण नहीं है .. बल्कि इसलिए कि आपके कोड को मशीन कोड में कैसे अनुवादित किया जाएगा।

तो १० से १०० तक की गिनती १०० से १० तक की गिनती के समान है
लेकिन १ से १० तक गिनती = १ से ० तक की
गिनती i = ० से १०० तक तेज है - ज्यादातर मामलों में और i = N से ० तक की गिनती i = से तेज है 0 से एन

  • ध्यान दें कि आजकल कंपाइलर आपके लिए यह अनुकूलन कर सकते हैं (यदि यह काफी स्मार्ट है)
  • यह भी ध्यान दें कि पाइपलाइन बेलाडी के विसंगति का कारण बन सकती है जैसा प्रभाव हो सकता है (यह सुनिश्चित नहीं किया जा सकता है कि क्या बेहतर होगा)
  • अंत में: कृपया ध्यान दें कि आपके द्वारा प्रस्तुत किए गए छोरों के लिए 2 बराबर नहीं हैं .. पहला प्रिंट एक और * ...।

संबंधित: n + n + n + 1 की तुलना में n ++ अधिक तेज़ी से क्यों निष्पादित होता है?


6
इसलिए आप जो कह रहे हैं, वह गिनती के लिए तेज़ नहीं है, यह किसी भी अन्य मूल्य की तुलना में शून्य की तुलना में तेज़ है। मतलब १० से १०० तक की गिनती और १०० से १० तक की गिनती एक ही होगी?
बॉब

8
हां .. यह "नीचे या ऊपर की गिनती" की बात नहीं है .. लेकिन यह "क्या की तुलना" करने की बात है ..
बेटमू

3
जबकि यह कोडांतरक स्तर सही है। दो चीजें वास्तविकता में असत्य को जोड़ती हैं - लंबे पाइप और सट्टा निर्देश का उपयोग करते हुए आधुनिक हार्डवेयर "सब i और N" में एक अतिरिक्त चक्र को आघात किए बिना चुपके करेगा - और - यहां तक ​​कि सबसे खराब कंपाइलर "सब आई" का अनुकूलन करेगा एन “अस्तित्व से बाहर।
जेम्स एंडरसन

2
@ निको एक प्राचीन प्रणाली नहीं है। यह सिर्फ एक निर्देश सेट होना है जहां शून्य ऑपरेशन की तुलना है जो रजिस्टर मूल्य की तुलना में किसी भी तरह से तेज / बेहतर है। x86 में इसे jcxz है। x64 अभी भी है। प्राचीन नहीं है। इसके अलावा, आरआईएससी आर्किटेक्चर अक्सर विशेष-केस शून्य होते हैं। DEC AXP अल्फा चिप (MIPS परिवार में), उदाहरण के लिए, एक "शून्य रजिस्टर" था - शून्य के रूप में पढ़ा जाता है, लिखना कुछ भी नहीं करता है। एक सामान्य रजिस्टर के मुकाबले शून्य रजिस्टर की तुलना में एक शून्य मान होता है जो अंतर अनुदेश निर्भरता को कम करता है और ऑर्डर निष्पादन से बाहर निकलने में मदद करता है।
dthorpe

5
@ बट्टमू: मैं अक्सर सोचता हूं कि बेहतर / अधिक सही उत्तर क्यों नहीं हैं (जो कि आपका है) अधिक मतों से सराहे जाते हैं और इस निष्कर्ष पर आते हैं कि स्टैकओवरफ्लो वोटों पर भी अक्सर किसी व्यक्ति की प्रतिष्ठा (बिंदुओं) से प्रभावित होते हैं जो जवाब देते हैं ( जो बहुत खराब है) और उत्तर शुद्धता से नहीं
आर्टूर

12

C से psudo विधानसभा में:

for (i = 0; i < 10; i++) {
    foo(i);
}

में बदल जाता है

    clear i
top_of_loop:
    call foo
    increment i
    compare 10, i
    jump_less top_of_loop

जबकि:

for (i = 10; i >= 0; i--) {
    foo(i);
}

में बदल जाता है

    load i, 10
top_of_loop:
    call foo
    decrement i
    jump_not_neg top_of_loop

दूसरी छद्म विधानसभा में तुलना की कमी पर ध्यान दें। कई आर्किटेक्चर पर ऐसे ध्वज हैं जो अंकगणितीय संचालन (जोड़, घटाना, गुणा, भाग, वृद्धि, क्षय) द्वारा निर्धारित किए जाते हैं जिनका उपयोग आप जंप के लिए कर सकते हैं। ये अक्सर आपको देते हैं कि अनिवार्य रूप से मुफ्त में 0 के साथ ऑपरेशन के परिणाम की तुलना क्या है। वास्तव में कई आर्किटेक्चर पर

x = x - 0

शब्दार्थ समान है

compare x, 0

इसके अलावा, मेरे उदाहरण में 10 के मुकाबले की तुलना में खराब कोड हो सकता है। 10 को एक रजिस्टर में रहना पड़ सकता है, इसलिए यदि वे कम आपूर्ति में हैं तो लागत और अतिरिक्त कोड के परिणामस्वरूप चीजों को घूमने या लूप के माध्यम से हर बार 10 को फिर से लोड करना पड़ सकता है।

कंपाइलर कभी-कभी इसका फायदा उठाने के लिए कोड को फिर से व्यवस्थित कर सकते हैं, लेकिन यह अक्सर मुश्किल होता है क्योंकि वे अक्सर यह सुनिश्चित करने में असमर्थ होते हैं कि लूप के माध्यम से दिशा को उलट करना शब्दार्थ के बराबर है।


क्या यह संभव है कि केवल 1 के बजाय 2 निर्देशों का एक अंतर है?
पेसियर

इसके अलावा, यह सुनिश्चित करना कठिन क्यों है? जब तक var iका उपयोग लूप के भीतर नहीं किया जाता है, जाहिर है कि आप इसे फ्लिप कर सकते हैं यह नहीं है?
पेसियर

6

इस तरह से तेजी से गिनती करें:

for (i = someObject.getAllObjects.size(); i >= 0; i--) {…}

क्योंकि someObject.getAllObjects.size()शुरुआत में एक बार निष्पादित होता है।


निश्चित size()रूप से, लूप से कॉल करके समान व्यवहार प्राप्त किया जा सकता है , जैसा कि पीटर ने उल्लेख किया है:

size = someObject.getAllObjects.size();
for (i = 0; i < size; i++) {…}

5
यह "निश्चित रूप से तेज" नहीं है। कई मामलों में आकार () कॉल को लूप से बाहर निकालते समय गिना जा सकता है, इसलिए यह अभी भी केवल एक बार कॉल किया जाएगा। जाहिर है कि यह भाषा और संकलक आश्रित (और कोड आश्रित; उदा। C ++ में यह फहराया नहीं जाएगा यदि आकार) () आभासी है, लेकिन यह निश्चित तरीके से बहुत दूर है।
पीटर

3
@ पेटर: केवल अगर कंपाइलर कुछ निश्चित आकार के लिए जानता है () लूप भर में बेकार है। यह शायद लगभग हमेशा नहीं होता है, जब तक कि लूप बहुत सरल नहीं होता है।
लॉरेंस Dol

@ लॉरेंसडोल, कंपाइलर निश्चित रूप से यह तब तक पता चलेगा जब तक कि आपके पास डायनामिक कोड कंपिलटिनो का उपयोग न हो exec
पचेरियर

4

क्या यह तेजी से ऊपर से नीचे गिना जाता है?

शायद। लेकिन 99% से अधिक समय यह मायने नहीं रखता है, इसलिए आपको लूप को समाप्त करने के लिए सबसे 'समझदार' परीक्षण का उपयोग करना चाहिए, और समझदार होने से मेरा मतलब है कि पाठक द्वारा यह पता लगाने के लिए कम से कम मात्रा में सोचा जाए। लूप क्या कर रहा है (इसमें क्या रुक जाता है सहित)। अपने कोड को मानसिक (या प्रलेखित) मॉडल से मिलान करें कि कोड क्या कर रहा है।

यदि लूप काम कर रहा है, तो यह एक सरणी (या सूची, या जो भी हो) के माध्यम से हो रहा है, एक इंक्रीमेंटिंग काउंटर अक्सर बेहतर होगा कि पाठक कैसे सोच रहा है कि लूप क्या कर रहा है - अपने लूप को इस तरह से कोड करें।

लेकिन अगर आप एक कंटेनर के माध्यम से काम कर रहे हैं जो है N आइटम हैं, और आप जाते ही वस्तुओं को हटा रहे हैं, तो काउंटर को काम करने के लिए अधिक संज्ञानात्मक समझ हो सकती है।

उत्तर में 'शायद' पर थोड़ा और विस्तार:

यह सच है कि अधिकांश आर्किटेक्चर पर, गणना के लिए परीक्षण जिसके परिणामस्वरूप शून्य (या शून्य से ऋणात्मक तक जा रहा है) के लिए कोई स्पष्ट परीक्षण निर्देश की आवश्यकता नहीं है - परिणाम सीधे जांचा जा सकता है। यदि आप यह परीक्षण करना चाहते हैं कि क्या गणना किसी अन्य संख्या में परिणाम देती है, तो निर्देश धारा में आम तौर पर उस मूल्य के लिए परीक्षण करने के लिए एक स्पष्ट निर्देश होना चाहिए। हालांकि, विशेष रूप से आधुनिक सीपीयू के साथ, यह परीक्षण आमतौर पर शोर-स्तर अतिरिक्त समय से कम एक लूपिंग निर्माण में जोड़ देगा। विशेष रूप से अगर वह लूप I / O प्रदर्शन कर रहा है।

दूसरी ओर, यदि आप शून्य से नीचे की गिनती करते हैं, और काउंटर को एक सरणी सूचकांक के रूप में उपयोग करते हैं, उदाहरण के लिए, आपको सिस्टम की मेमोरी आर्किटेक्चर के खिलाफ काम करने वाला कोड मिल सकता है - मेमोरी रीड अक्सर एक कैश को 'आगे देखो' का कारण बनेगा कई मेमोरी स्थान एक अनुक्रमिक पढ़ने की प्रत्याशा में वर्तमान एक को पिछले करते हैं। यदि आप मेमोरी के माध्यम से पीछे की ओर काम कर रहे हैं, तो कैशिंग सिस्टम मेमोरी मेमोरी स्थान के निचले मेमोरी पते पर रीड्स का अनुमान नहीं लगा सकता है। इस मामले में, यह संभव है कि लूपिंग 'बैकवर्ड' प्रदर्शन को नुकसान पहुंचा सकता है। हालाँकि, मैं अब भी शायद लूप को इस तरह से कोड करूँगा (जब तक प्रदर्शन एक मुद्दा नहीं बन जाता) क्योंकि शुद्धता सर्वोपरि है, और कोड मैच को मॉडल बनाना एक सही तरीका है जिससे शुद्धता सुनिश्चित करने में मदद मिलेगी। गलत कोड उतना ही अनधिकृत है जितना आप प्राप्त कर सकते हैं।

इसलिए मैं प्रोफेसर की सलाह को भूल जाऊंगा (बेशक, उसके परीक्षण पर नहीं - हालांकि आपको अभी भी व्यावहारिक होना चाहिए, जहां तक ​​कक्षा जाती है), जब तक और कोड के प्रदर्शन पर वास्तव में कोई फर्क नहीं पड़ता।


3

कुछ पुराने सीपीयू पर DJNZ== जैसे निर्देश थे "शून्य नहीं होने पर वेतन वृद्धि और कूदना"। यह उन कुशल छोरों के लिए अनुमति देता है जहां आपने एक रजिस्टर में एक प्रारंभिक गणना मूल्य लोड किया था और फिर आप एक निर्देश के साथ एक प्रभावी लूप का प्रबंधन कर सकते थे। हम यहाँ 1980 के दशक के आईएसए के बारे में बात कर रहे हैं - यदि आपका शिक्षक गंभीरता से सोचता है कि उसे लगता है कि यह "नियम नियम" अभी भी आधुनिक सीपीयू के साथ लागू होता है।


3

बॉब,

नहीं जब तक आप microoptimifications कर रहे हैं, जिस बिंदु पर आपके पास अपने सीपीयू को हाथ करने के लिए मैनुअल होगा। इसके अलावा, यदि आप इस तरह की बात कर रहे थे, तो आपको इस प्रश्न को वैसे भी पूछने की आवश्यकता नहीं होगी। :-) लेकिन, आपका शिक्षक स्पष्ट रूप से उस विचार की सदस्यता नहीं लेता है ...।

आपके पाश उदाहरण में विचार करने के लिए 4 चीजें हैं:

for (i=N; 
 i>=0;             //thing 1
 i--)             //thing 2
{
  putchar('*');   //thing 3
}
  • तुलना

तुलना (जैसा कि अन्य ने संकेत दिया है) विशेष प्रोसेसर आर्किटेक्चर के लिए प्रासंगिक है । विंडोज चलाने वालों की तुलना में अधिक प्रकार के प्रोसेसर हैं। विशेष रूप से, एक निर्देश हो सकता है जो 0 के साथ तुलना को सरल और तेज करता है।

  • समायोजन

कुछ मामलों में, ऊपर या नीचे समायोजित करने के लिए यह तेज है। आम तौर पर एक अच्छा संकलक यह पता लगाएगा और यदि यह कर सकता है तो लूप को फिर से करें। हालांकि सभी कंपाइलर अच्छे नहीं हैं।

  • पाश शरीर

आप पुटचर के साथ एक syscall का उपयोग कर रहे हैं। यह बड़े पैमाने पर धीमा है। इसके अलावा, आप स्क्रीन पर अप्रत्यक्ष रूप से प्रतिपादन कर रहे हैं। वह भी धीमा है। सोचो 1000: 1 अनुपात या अधिक। इस स्थिति में, लूप शरीर पूरी तरह से और पूरी तरह से लूप समायोजन / तुलना की लागत से आगे निकल जाता है।

  • कैश

एक कैश और मेमोरी लेआउट प्रदर्शन पर बड़ा प्रभाव डाल सकता है। इस स्थिति में, इससे कोई फर्क नहीं पड़ता। हालाँकि, यदि आप किसी ऐरे से एक्सेस कर रहे हैं और इष्टतम प्रदर्शन की आवश्यकता है, तो यह जांचना आपके लिए होगा कि आपके कंपाइलर और आपके प्रोसेसर ने मेमोरी एक्सेस को कैसे निर्धारित किया है और आपके सॉफ़्टवेयर को ट्यून करने के लिए। स्टॉक का उदाहरण मैट्रिक्स गुणा के संबंध में दिया गया एक है।


3

आपके काउंटर को बढ़ाने या घटाने से अधिक मायने रखता है कि क्या आप मेमोरी या डाउन मेमोरी में जा रहे हैं। अधिकांश कैश को मेमोरी में जाने के लिए ऑप्टिमाइज़ किया जाता है, मेमोरी को डाउन करने के लिए नहीं। चूँकि मेमोरी एक्सेस टाइम वह अड़चन है जिसका अधिकांश कार्यक्रम आज सामना करते हैं, इसका मतलब यह है कि आपके प्रोग्राम को बदलने से आप मेमोरी को बढ़ा सकते हैं, भले ही इसके लिए आपको अपने काउंटर की गैर-शून्य मान से तुलना करने की आवश्यकता हो, फिर भी प्रदर्शन में वृद्धि हो सकती है। अपने कुछ कार्यक्रमों में, मैंने अपने कोड को नीचे की बजाय मेमोरी में बदलने के लिए प्रदर्शन में महत्वपूर्ण सुधार देखा।

उलझन में? बस समय-समय पर लूप्स को ऊपर / नीचे मेमोरी में जाने का प्रोग्राम लिखें। यहाँ आउटपुट है जो मुझे मिला है:

Average Up Memory   = 4839 mus
Average Down Memory = 5552 mus

Average Up Memory   = 18638 mus
Average Down Memory = 19053 mus

(जहाँ "मस्क" का अर्थ माइक्रोसेकंड है) इस कार्यक्रम को चलाने से:

#include <chrono>
#include <iostream>
#include <random>
#include <vector>

//Sum all numbers going up memory.
template<class Iterator, class T>
inline void sum_abs_up(Iterator first, Iterator one_past_last, T &total) {
  T sum = 0;
  auto it = first;
  do {
    sum += *it;
    it++;
  } while (it != one_past_last);
  total += sum;
}

//Sum all numbers going down memory.
template<class Iterator, class T>
inline void sum_abs_down(Iterator first, Iterator one_past_last, T &total) {
  T sum = 0;
  auto it = one_past_last;
  do {
    it--;
    sum += *it;
  } while (it != first);
  total += sum;
}

//Time how long it takes to make num_repititions identical calls to sum_abs_down().
//We will divide this time by num_repitions to get the average time.
template<class T>
std::chrono::nanoseconds TimeDown(std::vector<T> &vec, const std::vector<T> &vec_original,
                                  std::size_t num_repititions, T &running_sum) {
  std::chrono::nanoseconds total{0};
  for (std::size_t i = 0; i < num_repititions; i++) {
    auto start_time = std::chrono::high_resolution_clock::now();
    sum_abs_down(vec.begin(), vec.end(), running_sum);
    total += std::chrono::high_resolution_clock::now() - start_time;
    vec = vec_original;
  }
  return total;
}

template<class T>
std::chrono::nanoseconds TimeUp(std::vector<T> &vec, const std::vector<T> &vec_original,
                                std::size_t num_repititions, T &running_sum) {
  std::chrono::nanoseconds total{0};
  for (std::size_t i = 0; i < num_repititions; i++) {
    auto start_time = std::chrono::high_resolution_clock::now();
    sum_abs_up(vec.begin(), vec.end(), running_sum);
    total += std::chrono::high_resolution_clock::now() - start_time;
    vec = vec_original;
  }
  return total;
}

template<class Iterator, typename T>
void FillWithRandomNumbers(Iterator start, Iterator one_past_end, T a, T b) {
  std::random_device rnd_device;
  std::mt19937 generator(rnd_device());
  std::uniform_int_distribution<T> dist(a, b);
  for (auto it = start; it != one_past_end; it++)
    *it = dist(generator);
  return ;
}

template<class Iterator>
void FillWithRandomNumbers(Iterator start, Iterator one_past_end, double a, double b) {
  std::random_device rnd_device;
  std::mt19937_64 generator(rnd_device());
  std::uniform_real_distribution<double> dist(a, b);
  for (auto it = start; it != one_past_end; it++)
    *it = dist(generator);
  return ;
}

template<class ValueType>
void TimeFunctions(std::size_t num_repititions, std::size_t vec_size = (1u << 24)) {
  auto lower = std::numeric_limits<ValueType>::min();
  auto upper = std::numeric_limits<ValueType>::max();
  std::vector<ValueType> vec(vec_size);

  FillWithRandomNumbers(vec.begin(), vec.end(), lower, upper);
  const auto vec_original = vec;
  ValueType sum_up = 0, sum_down = 0;

  auto time_up   = TimeUp(vec, vec_original, num_repititions, sum_up).count();
  auto time_down = TimeDown(vec, vec_original, num_repititions, sum_down).count();
  std::cout << "Average Up Memory   = " << time_up/(num_repititions * 1000) << " mus\n";
  std::cout << "Average Down Memory = " << time_down/(num_repititions * 1000) << " mus"
            << std::endl;
  return ;
}

int main() {
  std::size_t num_repititions = 1 << 10;
  TimeFunctions<int>(num_repititions);
  std::cout << '\n';
  TimeFunctions<double>(num_repititions);
  return 0;
}

दोनों sum_abs_upऔर sum_abs_downएक ही काम करते हैं (संख्याओं के सदिश राशि) और केवल अंतर के साथ उसी तरह से समयबद्ध होते हैं जो sum_abs_upस्मृति जाते समय स्मृति के ऊपर sum_abs_downजाती है। मैं vecसंदर्भ से भी गुजरता हूं ताकि दोनों फ़ंक्शन समान मेमोरी स्थानों तक पहुंच सकें। फिर भी, sum_abs_upलगातार तेजी से है sum_abs_down। इसे स्वयं चलाएं (मैंने इसे g ++ -O3 के साथ संकलित किया)।

यह नोट करना महत्वपूर्ण है कि मैं कितना लूप टाइट कर रहा हूं। यदि एक लूप का शरीर बड़ा है, तो यह संभवत: कोई फर्क नहीं पड़ता कि इसका पुनरावृत्ति मेमोरी में ऊपर या नीचे जाता है क्योंकि लूप के शरीर को निष्पादित करने में लगने वाला समय पूरी तरह से हावी होगा। इसके अलावा, यह उल्लेख करना महत्वपूर्ण है कि कुछ दुर्लभ छोरों के साथ, स्मृति को नीचे जाना कभी-कभी इसे ऊपर जाने से तेज होता है। लेकिन इस तरह के छोरों के साथ भी ऐसा कभी नहीं हुआ था कि स्मृति हमेशा ऊपर जाती थी की तुलना धीमी होती थी (छोटे शरीर वाले छोरों के विपरीत, जो मेमोरी में ऊपर जाती हैं, जिसके लिए विपरीत अक्सर सही होता है; वास्तव में, एक छोटे से मुट्ठी भर छोरों के लिए) समय सीमा समाप्त हो गई, मेमोरी बढ़ने से प्रदर्शन में 40% की वृद्धि हुई)।

बिंदु, अंगूठे के एक नियम के रूप में, यदि आपके पास विकल्प है, अगर लूप का शरीर छोटा है, और यदि आपके लूप के नीचे जाने के बजाय इसकी मेमोरी में थोड़ा अंतर है, तो आपको मेमोरी को ऊपर ले जाना चाहिए।

एफवाईआई vec_originalप्रयोग के लिए है, इसे बदलने में आसान बनाने के लिए sum_abs_upऔर भविष्य में समय को प्रभावित करने के लिए इन परिवर्तनों को अनुमति नहीं देते हुए sum_abs_downउन्हें बदल vecदेता है। मैं अत्यधिक के साथ प्रयोग करना की सलाह देते हैं sum_abs_upऔर sum_abs_downऔर परिणाम समय।


2

दिशा की परवाह किए बिना हमेशा उपसर्ग फॉर्म का उपयोग करें (i ++ के बजाय ++)!

for (i=N; i>=0; --i)  

या

for (i=0; i<N; ++i) 

स्पष्टीकरण: http://www.eskimo.com/~scs/cclass/notes/sx7b.html

इसके अलावा आप लिख सकते हैं

for (i=N; i; --i)  

लेकिन मुझे उम्मीद है कि आधुनिक कंपाइलर इन ऑप्टिमाइज़ेशन को करने में सक्षम होंगे।


इससे पहले कभी भी लोगों ने इसकी शिकायत नहीं देखी। लेकिन लिंक पढ़ने के बाद यह वास्तव में समझ में आता है :) धन्यवाद।
टॉमी जैकबसेन

3
उम, वह हमेशा उपसर्ग फॉर्म का उपयोग क्यों करना चाहिए? यदि कोई असाइनमेंट नहीं चल रहा है, तो वे समान हैं, और आपके द्वारा लिंक किया गया लेख भी कहता है कि पोस्टफ़िक्स फॉर्म अधिक सामान्य है।
22

3
हमेशा उपसर्ग फॉर्म का उपयोग क्यों करना चाहिए? इस उदाहरण में, यह शब्दार्थ समान है।
बेन जोतो

2
पोस्टफ़िक्स फॉर्म संभवतः ऑब्जेक्ट की एक अनावश्यक प्रतिलिपि बना सकता है, हालांकि यदि मूल्य का उपयोग कभी नहीं किया जा रहा है, तो कंपाइलर शायद इसे उपसर्ग फॉर्म में वैसे भी अनुकूलित करेगा।
निक लुईस

आदत के बल से, मैं हमेशा --i और i ++ करता हूं क्योंकि जब मैंने सीखा कि सी कंप्यूटर में आमतौर पर एक रजिस्टर गड़बड़ी और पश्चाताप होता है, लेकिन इसके विपरीत नहीं। इस प्रकार, * p ++ और * - p * ++ p और * p-- की तुलना में तेज़ थे क्योंकि पूर्व दो एक 68000% कोड निर्देश में किए जा सकते थे।
जेरेमीप

2

यह एक दिलचस्प सवाल है, लेकिन एक व्यावहारिक मामले के रूप में मुझे नहीं लगता कि यह महत्वपूर्ण है और एक लूप को दूसरे से बेहतर नहीं बनाता है।

इस विकिपीडिया पृष्ठ के अनुसार: लीप सेकंड , "... सौर दिन मुख्य रूप से ज्वारीय घर्षण के कारण हर सदी में 1.7 मिसे लंबा हो जाता है।" लेकिन अगर आप अपने जन्मदिन तक दिनों की गिनती कर रहे हैं, तो क्या आप वास्तव में समय में इस छोटे अंतर की परवाह करते हैं?

यह अधिक महत्वपूर्ण है कि स्रोत कोड को पढ़ना और समझना आसान है। उन दो छोरों का एक अच्छा उदाहरण है कि पठनीयता क्यों महत्वपूर्ण है - वे एक ही समय में लूप नहीं करते हैं।

मैं शर्त लगाता हूं कि अधिकांश प्रोग्रामर पढ़ते हैं (i = 0; मैं <एन; मैं ++) और तुरंत समझ जाता हूं कि यह लूप एन बार। एक लूप (i = 1; i <= N; i ++), मेरे लिए वैसे भी, थोड़ा कम स्पष्ट है, और (i = N? I> 0; i--) मुझे इसके बारे में एक पल के लिए सोचना होगा। । अगर किसी सोच की आवश्यकता के बिना कोड का आशय सीधे मस्तिष्क में चला जाए तो यह सबसे अच्छा है।


दोनों निर्माण समझने में बिल्कुल आसान हैं। कुछ लोग हैं जो दावा करते हैं कि यदि आपके पास 3 या 4 पुनरावृत्तियाँ हैं, तो निर्देश को कॉपी करने से बेहतर है कि एक लूप बनाएं क्योंकि यह उनके लिए समझने में आसान है।
डेन्यूबियन नाविक

2

अजीब बात है, ऐसा लगता है कि वहाँ एक अंतर है। कम से कम, PHP में। निम्नलिखित बेंचमार्क पर विचार करें:

<?php

print "<br>".PHP_VERSION;
$iter = 100000000;
$i=$t1=$t2=0;

$t1 = microtime(true);
for($i=0;$i<$iter;$i++){}
$t2 = microtime(true);
print '<br>$i++ : '.($t2-$t1);

$t1 = microtime(true);
for($i=$iter;$i>0;$i--){}
$t2 = microtime(true);
print '<br>$i-- : '.($t2-$t1);

$t1 = microtime(true);
for($i=0;$i<$iter;++$i){}
$t2 = microtime(true);
print '<br>++$i : '.($t2-$t1);

$t1 = microtime(true);
for($i=$iter;$i>0;--$i){}
$t2 = microtime(true);
print '<br>--$i : '.($t2-$t1);

परिणाम दिलचस्प हैं:

PHP 5.2.13
$i++ : 8.8842368125916
$i-- : 8.1797409057617
++$i : 8.0271911621094
--$i : 7.1027431488037


PHP 5.3.1
$i++ : 8.9625310897827
$i-- : 8.5790238380432
++$i : 5.9647901058197
--$i : 5.4021768569946

अगर कोई जानता है कि क्यों, यह जानना अच्छा होगा :)

संपादित करें : यदि आप 0 से नहीं, बल्कि अन्य मनमाना मूल्य गिनना शुरू करते हैं, तो परिणाम समान होते हैं। तो शायद शून्य की तुलना ही नहीं है जिससे फर्क पड़ता है?


यह धीमा होने का कारण यह है कि उपसर्ग ऑपरेटर को एक अस्थायी स्टोर करने की आवश्यकता नहीं है। $ फू = $ i ++ पर विचार करें; तीन चीजें होती हैं: $ i को अस्थायी में संग्रहीत किया जाता है, $ i को बढ़ाया जाता है, और फिर $ foo को उस अस्थायी मान को सौंपा जाता है। $ I ++ के मामले में; एक स्मार्ट संकलक महसूस कर सकता है कि अस्थायी अनावश्यक है। PHP बस नहीं है। इस सरल अनुकूलन को बनाने के लिए C ++ और जावा कंपाइलर काफी स्मार्ट हैं।
कॉन्सिफिक कंपाइलर

और $ i-- $ i ++ से अधिक तेज़ क्यों है?
टी.एस.

आपने अपने बेंचमार्क के कितने पुनरावृत्तियों को चलाया? क्या आपने आउटराइडर्स को क्लिप किया और प्रत्येक परिणाम के लिए औसत लिया? क्या आपका कंप्यूटर बेंचमार्क के दौरान कुछ और कर रहा था? यह ~ 0.5 अंतर सिर्फ अन्य सीपीयू गतिविधि, या पाइपलाइन उपयोग, या ... या ... का परिणाम हो सकता है, ठीक है, आपको यह विचार मिलता है।
आठ-बिट गुरु

हां, यहां मैं औसत दे रहा हूं। बेंचमार्क विभिन्न मशीनों पर चलाया गया था, और अंतर आकस्मिक रूप से है।
टी.एस.

@Conspicuous Compiler => आप जानते हैं या आप मानते हैं?
टी.एस.

2

यह तेज हो सकता है।

NIOS II प्रोसेसर पर मैं वर्तमान में लूप के लिए पारंपरिक के साथ काम कर रहा हूं

for(i=0;i<100;i++)

विधानसभा का निर्माण करता है:

ldw r2,-3340(fp) %load i to r2
addi r2,r2,1     %increase i by 1
stw r2,-3340(fp) %save value of i
ldw r2,-3340(fp) %load value again (???)
cmplti r2,r2,100 %compare if less than equal 100
bne r2,zero,0xa018 %jump

अगर हम गिनते हैं

for(i=100;i--;)

हमें एक असेंबली मिलती है जिसमें 2 निर्देशों की आवश्यकता होती है।

ldw r2,-3340(fp)
addi r3,r2,-1
stw r3,-3340(fp)
bne r2,zero,0xa01c

यदि हमारे पास नेस्टेड लूप हैं, जहां आंतरिक लूप को बहुत निष्पादित किया जाता है, तो हमारे पास एक औसत दर्जे का अंतर हो सकता है:

int i,j,a=0;
for(i=100;i--;){
    for(j=10000;j--;){
        a = j+1;
    }
}

यदि आंतरिक लूप ऊपर की तरह लिखा गया है, तो निष्पादन समय है: 0.12199999999999999734 सेकंड। यदि आंतरिक लूप को पारंपरिक तरीके से लिखा जाता है, तो निष्पादन का समय होता है: 0.17199999999999998623 सेकंड। इसलिए लूप काउंटिंग लगभग 30% तेज है।

लेकिन: यह परीक्षण सभी जीसीसी अनुकूलन बंद कर दिया गया था। यदि हम उन्हें चालू करते हैं, तो संकलक वास्तव में इस हैंडसम अनुकूलन की तुलना में अधिक स्मार्ट होता है और यहां तक ​​कि पूरे लूप के दौरान एक रजिस्टर में मूल्य रखता है और हमें एक असेंबली मिल जाएगी

addi r2,r2,-1
bne r2,zero,0xa01c

इस विशेष उदाहरण में संकलक ने भी नोटिस किया, कि लूप निष्पादन के बाद वेरिएबल 1 रास्ते हो जाएगा और लूप को पूरी तरह से छोड़ देगा।

हालाँकि मैंने अनुभव किया कि कभी-कभी यदि लूप बॉडी पर्याप्त जटिल होती है, तो कंपाइलर इस अनुकूलन को करने में सक्षम नहीं होता है, इसलिए हमेशा एक तेज़ लूप निष्पादन प्राप्त करने का सबसे सुरक्षित तरीका लिखना है:

register int i;
for(i=10000;i--;)
{ ... }

बेशक यह केवल काम करता है, अगर यह कोई फर्क नहीं पड़ता कि लूप को रिवर्स में निष्पादित किया जाता है और जैसे बेतामू ने कहा, केवल अगर आप शून्य से नीचे की ओर गिन रहे हैं।


2

आपके शिक्षक ने जो कहा है, वह बिना किसी स्पष्टीकरण के कुछ तिरस्कारपूर्ण है। ऐसा नहीं है कि वेतन वृद्धि वेतन वृद्धि की तुलना में तेज है लेकिन आप वेतन वृद्धि की तुलना में वेतन वृद्धि के साथ बहुत तेजी से लूप बना सकते हैं।

इसके बारे में लंबाई में जाने के बिना, लूप काउंटर आदि के उपयोग की आवश्यकता के बिना - नीचे जो मायने रखता है वह सिर्फ गति और लूप काउंट (गैर शून्य) है।

यहां बताया गया है कि अधिकांश लोग 10 पुनरावृत्तियों के साथ लूप कैसे लागू करते हैं:

int i;
for (i = 0; i < 10; i++)
{
    //something here
}

99% मामलों में इसके लिए सभी की आवश्यकता हो सकती है लेकिन PHP, PYTHON, JavaScript के साथ-साथ समय की पूरी दुनिया महत्वपूर्ण सॉफ्टवेयर (आमतौर पर एम्बेडेड, OS, गेम आदि) है जहां CPU टिक वास्तव में मायने रखते हैं इसलिए विधानसभा कोड में संक्षेप में देखें:

int i;
for (i = 0; i < 10; i++)
{
    //something here
}

संकलन के बाद (अनुकूलन के बिना) संकलित संस्करण इस तरह दिखाई दे सकता है (VS2015):

-------- C7 45 B0 00 00 00 00  mov         dword ptr [i],0  
-------- EB 09                 jmp         labelB 
labelA   8B 45 B0              mov         eax,dword ptr [i]  
-------- 83 C0 01              add         eax,1  
-------- 89 45 B0              mov         dword ptr [i],eax  
labelB   83 7D B0 0A           cmp         dword ptr [i],0Ah  
-------- 7D 02                 jge         out1 
-------- EB EF                 jmp         labelA  
out1:

पूरा लूप 8 निर्देश (26 बाइट्स) है। इसमें - 2 शाखाओं के साथ वास्तव में 6 निर्देश (17 बाइट्स) हैं। हां हां मुझे पता है कि इसे बेहतर किया जा सकता है (इसका सिर्फ एक उदाहरण)।

अब इस लगातार निर्माण पर विचार करें जो आपको अक्सर एम्बेडेड डेवलपर द्वारा लिखा गया मिलेगा:

i = 10;
do
{
    //something here
} while (--i);

यह 10 बार पुनरावृत्ति भी करता है (हाँ मुझे पता है कि मैं लूप के लिए दिखाए जाने के साथ तुलना में भिन्न है, लेकिन हम यहां पुनरावृत्ति गणना के बारे में ध्यान रखते हैं)। यह इस में संकलित किया जा सकता है:

00074EBC C7 45 B0 01 00 00 00 mov         dword ptr [i],1  
00074EC3 8B 45 B0             mov         eax,dword ptr [i]  
00074EC6 83 E8 01             sub         eax,1  
00074EC9 89 45 B0             mov         dword ptr [i],eax  
00074ECC 75 F5                jne         main+0C3h (074EC3h)  

5 निर्देश (18 बाइट्स) और सिर्फ एक शाखा। दरअसल लूप (11 बाइट्स) में 4 निर्देश होते हैं।

सबसे अच्छी बात यह है कि कुछ सीपीयू (x86 / x64 संगत) का निर्देश है कि एक रजिस्टर में वृद्धि हो सकती है, बाद में परिणाम की तुलना शून्य से करते हैं और यदि परिणाम शून्य से भिन्न होता है तो शाखा करते हैं। वस्तुतः सभी पीसी सीपीयू इस निर्देश को लागू करते हैं। लूप का उपयोग करना वास्तव में सिर्फ एक (हाँ एक) 2 बाइट अनुदेश है:

00144ECE B9 0A 00 00 00       mov         ecx,0Ah  
label:
                          // something here
00144ED3 E2 FE                loop        label (0144ED3h)  // decrement ecx and jump to label if not zero

क्या मुझे यह समझाना होगा कि कौन सा तेज है?

अब भले ही विशेष CPU उपर्युक्त निर्देश को लागू नहीं करता है, लेकिन इसका अनुकरण करने की आवश्यकता है, यह एक कमी है जिसके बाद सशर्त कूद जाता है यदि पिछले अनुदेश का परिणाम शून्य होता है।

तो कुछ मामलों की परवाह किए बिना कि आप एक टिप्पणी के रूप में इंगित कर सकते हैं कि मैं गलत क्यों हूं आदि आदि I IMPHASIZE - YES IT IS BENEFICIAL TO LOOP DOWNWARDS यदि आप जानते हैं कि कैसे, क्यों और कब।

पुनश्च। हाँ, मुझे पता है कि बुद्धिमान संकलक (उपयुक्त अनुकूलन स्तर के साथ) लूप के लिए फिर से लिखना होगा (आरोही लूप काउंटर के साथ) लगातार लूप पुनरावृत्तियों के लिए बराबर ... (या इसे अनियंत्रित करें) ...


1

नहीं, यह वास्तव में सच नहीं है। एक स्थिति जहां यह तेजी से हो सकती है, जब आप अन्यथा लूप के प्रत्येक पुनरावृत्ति के दौरान सीमा की जांच करने के लिए एक फ़ंक्शन कह रहे होंगे।

for(int i=myCollection.size(); i >= 0; i--)
{
   ...
}

लेकिन अगर यह उस तरह से कम स्पष्ट है, तो यह सार्थक नहीं है। आधुनिक भाषाओं में, जब भी संभव हो, आपको फ़ॉरच लूप का उपयोग करना चाहिए। आप विशेष रूप से उस मामले का उल्लेख करते हैं जहां आपको फ़ॉरच लूप का उपयोग करना चाहिए - जब आपको सूचकांक की आवश्यकता नहीं होती है।


1
स्पष्ट और कुशल होने के लिए आपको कम से कम आदत में होना चाहिए for(int i=0, siz=myCollection.size(); i<siz; i++)
लॉरेंस Dol

1

मुद्दा यह है कि जब गिनती करते हैं तो आपको i >= 0अलग से जांच करने की आवश्यकता नहीं होती है i। का निरीक्षण करें:

for (i = 5; i--;) {
  alert(i);  // alert boxes showing 4, 3, 2, 1, 0
}

तुलना और गिरावट दोनों iएक अभिव्यक्ति में किया जा सकता है।

अन्य उत्तरों को देखें कि यह कम x86 निर्देशों के लिए क्यों उबालता है।

जैसे कि क्या यह आपके आवेदन में एक सार्थक अंतर बनाता है, वैसे मुझे लगता है कि यह निर्भर करता है कि आपके पास कितने लूप हैं और वे कितने गहरे नेस्टेड हैं। लेकिन मेरे लिए, यह इस तरह से करने के लिए केवल पठनीय है, इसलिए मैं इसे वैसे भी करता हूं।


मुझे लगता है कि यह खराब शैली है, क्योंकि यह पाठक पर निर्भर करता है कि एक चक्र को बचाने के संभावित मूल्य के लिए i-- का वापसी मूल्य i का पुराना मूल्य है। यह तभी महत्वपूर्ण होगा जब लूप पुनरावृत्तियों के बहुत सारे थे, और चक्र पुनरावृत्ति की लंबाई का एक महत्वपूर्ण अंश था, और वास्तव में रन टाइम पर दिखाया गया था। अगला, कोई व्यक्ति (i = 5; -?) के लिए प्रयास करेगा क्योंकि उन्होंने सुना है कि C ++ में आप कुछ अस्थायी प्रकार से बचने से बचना चाहते हैं जब मैं एक गैर-तुच्छ प्रकार हूं, और अब आप बग भूमि में हैं गलत तरीके से गलत कोड बनाने के लिए अपने अवसर को दूर फेंक दिया।
शुभम्

0

अब, मुझे लगता है कि आपके पास पर्याप्त विधानसभा व्याख्यान थे :) मैं आपको शीर्ष- नीचे दृष्टिकोण के लिए एक और कारण पेश करना चाहूंगा।

ऊपर से जाने का कारण बहुत सरल है। लूप के शरीर में, आप गलती से सीमा को बदल सकते हैं, जो गलत व्यवहार या यहां तक ​​कि गैर-समाप्ति लूप में भी समाप्त हो सकता है।

जावा कोड के इस छोटे से हिस्से को देखें (भाषा इस कारण से मुझे फर्क नहीं पड़ता):

    System.out.println("top->down");
    int n = 999;
    for (int i = n; i >= 0; i--) {
        n++;
        System.out.println("i = " + i + "\t n = " + n);
    }
    System.out.println("bottom->up");
    n = 1;
    for (int i = 0; i < n; i++) {
        n++;
        System.out.println("i = " + i + "\t n = " + n);
    }

तो मेरी बात यह है कि आपको ऊपर से नीचे जाने या सीमा के रूप में स्थिर रहने पर विचार करना चाहिए।


हुह? !! आप असफल उदाहरण वास्तव में काउंटर-सहज ज्ञान युक्त हैं, जो यह कहना है, एक पुआल-आदमी तर्क - कोई भी कभी भी यह नहीं लिखेगा। एक लिखता था for (int i=0; i < 999; i++) {
लॉरेंस Dol

@ शेपर बंदर कुछ गणना के परिणामस्वरूप n होने की कल्पना करते हैं ... जैसे आप कुछ संग्रह पर पुनरावृति करना चाहते हैं और इसका आकार सीमा है, लेकिन कुछ साइड इफेक्ट के रूप में, आप लूप बॉडी में संग्रह में नए तत्व जोड़ते हैं।
गेब्रियल Gabričerbák

अगर आप संवाद करने का इरादा रखते हैं, तो आपके उदाहरण में इसका वर्णन होना चाहिए:for(int xa=0; xa<collection.size(); xa++) { collection.add(SomeObject); ... }
लॉरेंस Dol

@ सिस्टर मंकी मैं विशेष रूप से संग्रह के बारे में बात करने की तुलना में अधिक सामान्य होना चाहता था, क्योंकि मैं जिस बारे में तर्क दे रहा हूं उसका संग्रह से कोई लेना-देना नहीं है
गेब्रियल erčerbák

2
हां, लेकिन यदि आप उदाहरण के द्वारा तर्क करने जा रहे हैं, तो आपके उदाहरणों को बिंदु के विश्वसनीय और चित्रण की आवश्यकता है।
लॉरेंस डोल

-1

एक कोडांतरक स्तर पर एक लूप जो शून्य से नीचे गिना जाता है वह आम तौर पर एक से थोड़ा अधिक तेज होता है जो किसी दिए गए मूल्य तक गिना जाता है। यदि गणना का परिणाम शून्य के बराबर है तो अधिकांश प्रोसेसर शून्य ध्वज सेट करेंगे। यदि किसी को घटाकर पिछले शून्य के आसपास गणना की जाती है तो यह आम तौर पर कैरी फ्लैग को बदल देगा (कुछ प्रोसेसर पर यह इसे दूसरों पर सेट कर देगा यह इसे साफ कर देगा), इसलिए शून्य के साथ तुलना अनिवार्य रूप से मुफ्त में आती है।

यह और भी सच है जब पुनरावृत्तियों की संख्या एक स्थिर नहीं बल्कि एक चर है।

तुच्छ मामलों में कंपाइलर लूप की गिनती दिशा को स्वचालित रूप से अनुकूलित करने में सक्षम हो सकता है, लेकिन अधिक जटिल मामलों में यह हो सकता है कि प्रोग्रामर जानता है कि लूप की दिशा समग्र व्यवहार के लिए अप्रासंगिक है, लेकिन कंपाइलर यह साबित नहीं कर सकता है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.