क्या गैर-तुच्छ सशर्त बयानों को लूप के आरंभीकरण अनुभाग में ले जाना चाहिए?


21

इस सवाल से मुझे stackoverflow.com पर यह विचार आया

निम्नलिखित पैटर्न आम है:

final x = 10;//whatever constant value
for(int i = 0; i < Math.floor(Math.sqrt(x)) + 1; i++) {
  //...do something
}

मैं जिस बिंदु को बनाने की कोशिश कर रहा हूं वह है सशर्त बयान कुछ जटिल है और यह बदलता नहीं है।

क्या लूप के आरंभीकरण अनुभाग में इसे घोषित करना बेहतर है, जैसे?

final x = 10;//whatever constant value
for(int i = 0, j = Math.floor(Math.sqrt(x)) + 1; i < j; i++) {
  //...do something
}

क्या यह अधिक स्पष्ट है?

क्या होगा अगर सशर्त अभिव्यक्ति सरल है जैसे कि

final x = 10;//whatever constant value
for(int i = 0, j = n*n; i > j; j++) {
  //...do something
}

47
क्यों न केवल इसे लूप से पहले लाइन पर ले जाएं , फिर आप इसे एक समझदार नाम भी दे सकते हैं।
जोंशरशेप

2
@ मेहरदाद: वे समकक्ष नहीं हैं। यदि xपरिमाण में बड़ा है, Math.floor(Math.sqrt(x))+1के बराबर है Math.floor(Math.sqrt(x))। :-)
आर ..

5
@jonrsharpe क्योंकि यह वैरिएबल के दायरे को चौड़ा करेगा। मैं जरूरी नहीं कह रहा हूं कि ऐसा न करने का एक अच्छा कारण है, लेकिन यही कारण है कि कुछ लोग ऐसा नहीं करते हैं।
केविन क्रुमविडे

3
@KevinKrumwiede यदि स्कोप एक चिंता की सीमा है, तो कोड को अपने स्वयं के ब्लॉक में रखकर, जैसे, { x=whatever; for (...) {...} }या, बेहतर अभी तक, विचार करें कि क्या पर्याप्त चल रहा है कि इसे एक अलग फ़ंक्शन होने की आवश्यकता है।
१४:१५ पर ब्लरफ्ल

2
@jonrsharpe जब आप इसे init सेक्शन में भी घोषित करते हैं तो आप इसे एक समझदार नाम दे सकते हैं। यह नहीं कह रहा हूं कि मैं इसे वहां रखूंगा; यदि यह अलग है, तो पढ़ना अभी भी आसान है।
जॉलीजॉकर

जवाबों:


62

मैं कुछ ऐसा करूंगा:

void doSomeThings() {
    final x = 10;//whatever constant value
    final limit = Math.floor(Math.sqrt(x)) + 1;
    for(int i = 0; i < limit; i++) {
         //...do something
    }
}

ईमानदारी से लूप हेडर में आरम्भ j(अब limit) को रटने का एकमात्र अच्छा कारण है इसे सही ढंग से स्कोप रखना। यह सब करने में लगता है कि एक गैर मुद्दा एक अच्छा तंग घेरा गुंजाइश है।

मैं तेज होने की इच्छा की सराहना कर सकता हूं लेकिन एक वास्तविक अच्छे कारण के बिना पठनीयता का त्याग नहीं करता।

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


अच्छी बात। मैं मान सकता हूँ कि यदि कोई चीज़ कुछ सरल है तो दूसरे चर के साथ परेशान न करें, उदाहरण के लिए for(int i = 0; i < n*n; i++){...}आप n*nएक चर को नहीं सौंपेंगे ?
सेलेरिटास

1
मैं और मैं होगा। लेकिन गति के लिए नहीं। पठनीयता के लिए। पठनीय कोड अपने आप ही तेज़ हो जाता है।
कैंडिड_ऑरेंज

1
यहां तक ​​कि अगर आप एक निरंतर ( final) के रूप में चिह्नित करते हैं, तो यह डांटने का मुद्दा भी दूर हो जाता है । कौन परवाह करता है कि एक स्थिरांक जिसके पास संकलक प्रवर्तन है उसे बदलने से रोका जा सकता है जो बाद में फ़ंक्शन में सुलभ है?
jpmc26

मुझे लगता है कि एक बड़ी बात यह है कि आप ऐसा होने की उम्मीद करते हैं। मुझे पता है कि उदाहरण sqrt का उपयोग करता है लेकिन क्या होगा अगर यह एक और फ़ंक्शन था? क्या समारोह शुद्ध है? क्या आप हमेशा समान मूल्यों की उम्मीद कर रहे हैं? क्या इसके साइड इफेक्ट्स हैं? क्या साइड इफेक्ट्स कुछ हैं जो आप हर पुनरावृत्ति पर होना चाहते हैं?
Pieter B

38

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

तथापि...

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

मैं वास्तव में पसंद करूंगा कि इसे लूप के ऊपर से निकाला finalजाए और कोड को पढ़ने वाले किसी व्यक्ति को तुरंत और प्रचुरता से बनाया जाए। यह आदर्श नहीं है क्योंकि यह चर के दायरे को बढ़ाता है, लेकिन आपके एन्कोडिंग फ़ंक्शन को वैसे भी उस लूप से अधिक नहीं होना चाहिए।


5
एक बार जब आप फंक्शन कॉल्स को शामिल करना शुरू कर देते हैं, तो कंपाइलर को ऑप्टिमाइज़ करना बहुत मुश्किल हो जाता है। संकलक को विशेष पता होना चाहिए कि Math.sqrt का कोई दुष्प्रभाव नहीं है।
पीटर ग्रीन

@PeterGreen यदि यह जावा है, तो JVM इसका पता लगा सकता है, लेकिन इसमें कुछ समय लग सकता है।
क्राइसिस -ऑन स्ट्राइक-

प्रश्न को C ++ और Java दोनों में टैग किया गया है। मुझे नहीं पता कि जावा का JVM कितना उन्नत है और यह कब और कैसे पता लगा सकता है, लेकिन मुझे पता है कि C ++ कंपाइलर सामान्य रूप से यह पता नहीं लगा सकते हैं कि कोई फ़ंक्शन तब तक शुद्ध है जब तक कि यह एक गैर-मानक एनोटेशन नहीं है जो कंपाइलर को बता रहा है इसलिए, या फ़ंक्शन का शरीर दिखाई दे रहा है और सभी अप्रत्यक्ष रूप से कहे जाने वाले कार्यों को समान मानदंड से शुद्ध माना जा सकता है। नोट: लूप कंडीशन से बाहर निकलने के लिए साइड-इफ़ेक्ट-फ़्री पर्याप्त नहीं है। वैश्विक अवस्था पर निर्भर होने वाले कार्यों को लूप से बाहर नहीं निकाला जा सकता है यदि लूप बॉडी राज्य को संशोधित कर सकती है।
hvd

यह दिलचस्प हो जाता है जब एक बार Math.sqrt (x) को Mymodule.SomeNonPureMethodWithSideEffects (x) द्वारा बदल दिया जाता है।
पीटर बी

9

जैसा कि @ कार्ल बेलेफेल्ट ने अपने जवाब में कहा, यह आमतौर पर एक गैर-मुद्दा है।

हालाँकि, यह एक समय में C और C ++ में एक सामान्य मुद्दा था, और कोड की पठनीयता को कम किए बिना इस मुद्दे को साइड-स्टेप करने के बारे में एक ट्रिक आई- बैकअर्ट को नीचे की ओर0

final x = 10;//whatever constant value
for(int i = Math.floor(Math.sqrt(x)); i >= 0; i--) {
  //...do something
}

अब हर पुनरावृत्ति में सशर्त सिर्फ वह है >= 0जो प्रत्येक संकलक 1 या 2 विधानसभा निर्देशों में संकलित करेगा। पिछले कुछ दशकों में किए गए प्रत्येक सीपीयू में इन जैसे बुनियादी चेक होने चाहिए; अपने x64 मशीन पर एक त्वरित जांच कर रहा हूं, मैं देखता हूं कि यह अनुमानित रूप से बदल जाता है cmpl $0x0, -0x14(%rbp)(लंबी-अंतर-तुलना मूल्य 0 बनाम रजिस्टर आरपीपी ऑफसेट -14) और jl 0x100000f59(लूप के बाद निर्देश पर कूदें यदि पिछली तुलना "2-arg" के लिए सही थी <1st-arg ”)

ध्यान दें कि मैं हटाया + 1से Math.floor(Math.sqrt(x)) + 1; गणित के लिए वर्कआउट करने के लिए शुरुआती मूल्य होना चाहिए int i = «iterationCount» - 1। यह भी ध्यान देने योग्य है कि आपके पुनरावृत्ति पर हस्ताक्षर किए जाने चाहिए; unsigned intकाम नहीं करेगा और संकलक-चेतावनी की संभावना होगी।

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


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

4
यह ध्यान दिया जाना चाहिए कि आधुनिक संकलक अनुकूलन को "सामान्य" कोड को ध्यान में रखकर बनाया गया है। ज्यादातर मामलों में, कंपाइलर कोड उत्पन्न करेगा जो कि चाहे आप "ट्रिक्स" का उपयोग करें या नहीं, चाहे जितनी तेजी से हो। लेकिन कुछ चाल वास्तव में हो सकता है बाधा अनुकूलन कैसे जटिल के आधार पर वे कर रहे हैं। यदि कुछ ट्रिक्स का उपयोग करने से आपका कोड अधिक पढ़ने योग्य हो जाता है या सामान्य बग्स को पकड़ने में मदद करता है, तो यह बहुत अच्छा है, लेकिन अपने आप को यह सोचकर ट्रिक न करें "यदि मैं इस तरह से कोड लिखता हूं तो यह तेज होगा।" कोड लिखें कि आप सामान्य रूप से कैसे होंगे, फिर उन स्थानों को खोजने के लिए प्रोफ़ाइल करें जहां आपको अनुकूलन करने की आवश्यकता है।
0x5453

यह भी ध्यान दें कि unsignedकाउंटर यहां काम करेंगे यदि आप चेक को संशोधित करते हैं (सबसे आसान तरीका दोनों पक्षों के लिए समान मूल्य जोड़ना है); उदाहरण के लिए, किसी भी गिरावट के लिए Dec, चेक में (i + Dec) >= Decहमेशा दोनों और काउंटर के साथ signedचेक के समान परिणाम होना चाहिए , जब तक कि भाषा में चर के लिए अच्छी तरह से परिभाषित रैपराउंड नियम होते हैं (विशेष रूप से, दोनों के लिए सही होना चाहिए और )। ध्यान दें, हालांकि, यह एक हस्ताक्षरित चेक की तुलना में कम कुशल हो सकता है , अगर कंपाइलर के पास इसके लिए कोई अनुकूलन नहीं है। i >= 0signedunsignedunsigned-n + n == 0signedunsigned>=0
जस्टिन टाइम - मोनिका

1
@JustinTime हाँ, हस्ताक्षरित आवश्यकता सबसे कठिन हिस्सा है; Decशुरुआती मूल्य और समाप्ति मूल्य दोनों के लिए एक निरंतरता जोड़ना, लेकिन यह बहुत कम सहज बनाता है, और यदि iएक सरणी सूचकांक के रूप में उपयोग कर रहा है, तो आपको unsigned int arrayI = i - Dec;लूप बॉडी में भी करने की आवश्यकता होगी । जब मैं एक अहस्ताक्षरित itter के साथ अटक गया तो मैं आगे-पुनरावृत्ति का उपयोग करता हूं; अक्सर i <= count - 1तर्क-वितर्क छोरों के समानांतर तर्क रखने के लिए एक सशर्त के साथ ।
स्लिप डी। थॉम्पसन

1
@ SlippD.Thompson मेरा मतलब Decविशेष रूप से शुरुआती और अंत के मूल्यों को जोड़ना नहीं था , लेकिन Decदोनों तरफ से स्थिति की जांच करना । for (unsigned i = N - 1; i + 1 >= 1; i--) /*...*/ यह आपको iलूप के भीतर सामान्य रूप से उपयोग करने की अनुमति देता है , जबकि यह गारंटी देता है कि स्थिति के बाईं ओर सबसे कम संभव मूल्य है 0(रैपराउंड को हस्तक्षेप करने से रोकने के लिए)। यह निश्चित रूप से बहुत आसान जब अहस्ताक्षरित काउंटरों के साथ काम करने के लिए आगे यात्रा उपयोग करने के लिए है, हालांकि।
जस्टिन टाइम -

3

यह दिलचस्प हो जाता है जब एक बार Math.sqrt (x) को Mymodule.SomeNonPureMethodWithSideEffects (x) द्वारा बदल दिया जाता है।

मूल रूप से मेरा तौर-तरीका है: यदि किसी चीज से हमेशा समान मूल्य देने की अपेक्षा की जाती है, तो केवल एक बार उसका मूल्यांकन करें। उदाहरण के लिए List.Count, यदि लूप के संचालन के दौरान सूची को नहीं बदलना चाहिए, तो लूप के बाहर की गिनती को किसी अन्य चर में प्राप्त करें।

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


जब गिनती महंगी होती है, तो आपको इसका उपयोग बिल्कुल नहीं करना चाहिए। इसके बजाय आप के बराबर होना चाहिएfor( auto it = begin(dataset); !at_end(it); ++it )
बेन Voigt

@BenVoigt इटरेटर का उपयोग करना निश्चित रूप से उन परिचालनों के लिए सबसे अच्छा तरीका है। मैंने इसका उल्लेख साइड-इफेक्ट्स के साथ गैर-शुद्ध तरीकों का उपयोग करने के बारे में अपनी बात बताने के लिए किया है।
पीटर बी

0

मेरी राय में यह अत्यधिक विशिष्ट भाषा है। उदाहरण के लिए, यदि C ++ 11 का उपयोग करते हुए मुझे संदेह होगा कि यदि स्थिति जांच एक constexprफ़ंक्शन थी तो संकलक कई निष्पादन को अनुकूलित करने की बहुत संभावना होगी क्योंकि यह जानता है कि यह प्रत्येक बार समान मूल्य प्राप्त करेगा।

हालाँकि यदि फ़ंक्शन कॉल एक लाइब्रेरी फ़ंक्शन है जो constexprसंकलक नहीं है , तो निश्चित रूप से लगभग हर पुनरावृत्ति पर इसे निष्पादित करने वाला है क्योंकि यह इस कटौती नहीं कर सकता है (जब तक कि यह इनलाइन नहीं है और इसलिए इसे शुद्ध रूप में घटाया जा सकता है)।

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

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

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