यह मुझे ऐसा लगता है जैसे लोग किसी goto
बयान को बहुत नापसंद करते हैं , इसलिए मुझे इसे थोड़ा सा सीधा करने की आवश्यकता महसूस हुई।
मेरा मानना है कि लोगों की 'भावनाओं' के बारे में goto
अंततः कोड की समझ और संभावित प्रदर्शन निहितार्थ के बारे में गलत धारणाएं हैं। प्रश्न का उत्तर देने से पहले, मैं सबसे पहले कुछ विवरणों पर जाता हूँ कि यह कैसे संकलित है।
जैसा कि हम सभी जानते हैं, C # को IL में संकलित किया जाता है, जिसे बाद में एक SSA संकलक का उपयोग करके कोडांतरक के लिए संकलित किया जाता है। मैं यह सब कैसे काम करता है में थोड़ा सा जानकारी देता हूँ, और फिर सवाल का जवाब देने की कोशिश करता हूँ।
C # से IL तक
पहले हमें C # कोड का एक टुकड़ा चाहिए। चलिए शुरू करते हैं सरल:
foreach (var item in array)
{
// ...
break;
// ...
}
हुड के नीचे क्या होता है इसका एक अच्छा विचार देने के लिए मैं यह कदम से कदम उठाऊंगा।
पहला अनुवाद: foreach
समतुल्य for
लूप से (नोट: मैं यहाँ एक सरणी का उपयोग कर रहा हूँ, क्योंकि मैं आईडीसोफ़रोबल के विवरण में नहीं आना चाहता हूँ - जिस स्थिति में मुझे IEnumerable का उपयोग करना होगा):
for (int i=0; i<array.Length; ++i)
{
var item = array[i];
// ...
break;
// ...
}
दूसरा अनुवाद: for
और break
एक आसान बराबर में अनुवाद किया जाता है:
int i=0;
while (i < array.Length)
{
var item = array[i];
// ...
break;
// ...
++i;
}
और तीसरा अनुवाद (यह IL कोड के समतुल्य है): हम बदलते हैं break
और while
एक शाखा में:
int i=0; // for initialization
startLoop:
if (i >= array.Length) // for condition
{
goto exitLoop;
}
var item = array[i];
// ...
goto exitLoop; // break
// ...
++i; // for post-expression
goto startLoop;
जबकि संकलक इन चीजों को एक ही चरण में करता है, यह आपको प्रक्रिया में अंतर्दृष्टि प्रदान करता है। IL कोड जो C # प्रोग्राम से विकसित होता है, वह अंतिम C # कोड का शाब्दिक अनुवाद है। आप यहाँ अपने लिए देख सकते हैं: https://dotnetfiddle.net/QaiLRz ('आईएल देखें' पर क्लिक करें)
अब, आपके द्वारा यहां देखी गई एक बात यह है कि इस प्रक्रिया के दौरान, कोड अधिक जटिल हो जाता है। इसका पालन करने का सबसे आसान तरीका इस तथ्य से है कि हमें एक ही चीज़ को एक-दूसरे के लिए अधिक कोड की आवश्यकता थी। आप यह भी तर्क है कि हो सकता है foreach
, for
, while
और break
के लिए वास्तव में एक छोटी-हाथ कर रहे हैं goto
, जो आंशिक रूप से सही है।
IL से असेंबलर तक
.NET JIT कंपाइलर एक SSA कंपाइलर है। मैं यहां SSA फॉर्म के सभी विवरणों में नहीं जाऊंगा और एक अनुकूलन कंपाइलर कैसे बनाऊंगा, यह अभी बहुत ज्यादा है, लेकिन क्या होगा, इसके बारे में एक बुनियादी समझ दे सकते हैं। गहरी समझ के लिए, अनुकूलन करने वाले कंपाइलरों पर पढ़ना शुरू करना सबसे अच्छा है (मुझे यह पुस्तक एक संक्षिप्त परिचय के लिए पसंद है: http://ssabook.gforge.inria.fr/latest/book.pdf ) और LLVM (llvb.org) ।
हर अनुकूलन करने वाला संकलन इस तथ्य पर निर्भर करता है कि कोड आसान है और पूर्वानुमान योग्य पैटर्न का अनुसरण करता है । फॉर लूप के मामले में, हम शाखाओं का विश्लेषण करने के लिए ग्राफ सिद्धांत का उपयोग करते हैं, और फिर हमारी शाखाओं (जैसे शाखाएं) में साइक्ली जैसी चीजों का अनुकूलन करते हैं।
हालांकि, अब हमारे पास हमारे छोरों को लागू करने के लिए शाखाएं हैं। जैसा कि आपने अनुमान लगाया होगा, यह वास्तव में जेआईटी को ठीक करने जा रहे पहले कदमों में से एक है, जैसे:
int i=0; // for initialization
if (i >= array.Length) // for condition
{
goto endOfLoop;
}
startLoop:
var item = array[i];
// ...
goto endOfLoop; // break
// ...
++i; // for post-expression
if (i >= array.Length) // for condition
{
goto startLoop;
}
endOfLoop:
// ...
जैसा कि आप देख सकते हैं, हमारे पास अब एक पिछड़ी शाखा है, जो कि हमारा छोटा लूप है। केवल एक चीज जो अभी भी यहाँ बुरा है वह वह शाखा है जिसे हमने अपने break
बयान के कारण समाप्त किया । कुछ मामलों में, हम इसे उसी तरह से स्थानांतरित कर सकते हैं, लेकिन दूसरों में यह रहना है।
तो संकलक ऐसा क्यों करता है? ठीक है, अगर हम लूप को अनियंत्रित कर सकते हैं, तो हम इसे वेक्टर करने में सक्षम हो सकते हैं। हम इस बात का भी प्रमाण दे सकते हैं कि वहाँ केवल स्थिरांक जोड़े जा रहे हैं, जिसका अर्थ है कि हमारा पूरा पाश पतली हवा में लुप्त हो सकता है। संक्षेप में: पैटर्न को पूर्वानुमेय बनाकर (शाखाओं को पूर्वानुमेय बनाकर), हम इस बात का प्रमाण दे सकते हैं कि कुछ शर्तें हमारे पाश में हैं, जिसका अर्थ है कि हम जेआईटी अनुकूलन के दौरान जादू कर सकते हैं।
हालाँकि, शाखाएँ उन अच्छे पूर्वानुमान योग्य प्रतिमानों को तोड़ देती हैं, जो कि कुछ आशावादी होते हैं, इसलिए दयालु-नापसंद होते हैं। तोड़ो, जारी रखो, गोटो - वे सभी इन पूर्वानुमानित पैटर्न को तोड़ने का इरादा रखते हैं- और इसलिए वास्तव में 'अच्छा' नहीं हैं।
आपको इस बिंदु पर यह भी महसूस करना चाहिए कि एक सरल foreach
और अधिक पूर्वानुमान योग्य है, फिर goto
बयानों का एक गुच्छा जो सभी जगह जाता है। एक आशावादी दृष्टिकोण से (1) पठनीयता और (2) के संदर्भ में, यह दोनों ही बेहतर समाधान हैं।
एक और बात ध्यान देने योग्य है कि यह रजिस्टरों को चर ( रजिस्टर आवंटन नामक एक प्रक्रिया ) में रजिस्टरों को असाइन करने के लिए अनुकूलन के लिए बहुत प्रासंगिक है । जैसा कि आप जानते हैं, आपके CPU में केवल एक सीमित संख्या में रजिस्टर हैं और वे आपके हार्डवेयर में मेमोरी के सबसे तेज टुकड़े हैं। कोड में उपयोग किए जाने वाले चर जो सबसे आंतरिक लूप में होते हैं, एक रजिस्टर असाइन किए जाने की अधिक संभावना होती है, जबकि आपके लूप के बाहर चर कम महत्वपूर्ण होते हैं (क्योंकि यह कोड संभवतः कम मारा जाता है)।
मदद, बहुत अधिक जटिलता ... मुझे क्या करना चाहिए?
लब्बोलुआब यह है कि आपको हमेशा अपने निपटान में आपके द्वारा बनाई गई भाषा के निर्माण का उपयोग करना चाहिए, जो आमतौर पर (संक्षेप में) आपके कंपाइलर के लिए अनुमानित पैटर्न का निर्माण करेगा। (: विशेष रूप से यदि संभव हो तो अजीब शाखाओं से बचने के लिए प्रयास करें break
, continue
, goto
या एक return
कुछ भी नहीं के बीच में)।
यहाँ अच्छी खबर यह है कि ये प्रेडिक्टेबल पैटर्न दोनों (इंसानों के लिए) पढ़ना आसान है और स्पॉट करने के लिए आसान (कंपाइलर के लिए)।
उन पैटर्नों में से एक को SESE कहा जाता है, जो सिंगल एंट्री सिंगल एग्जिट के लिए है।
और अब हम असली सवाल पर आते हैं।
कल्पना कीजिए कि आपके पास ऐसा कुछ है:
// a is a variable.
for (int i=0; i<100; ++i)
{
for (int j=0; j<100; ++j)
{
// ...
if (i*j > a)
{
// break everything
}
}
}
इसे प्रेडिक्टेबल पैटर्न बनाने का सबसे आसान तरीका है कि इसे if
पूरी तरह से खत्म कर दिया जाए:
int i, j;
for (i=0; i<100 && i*j <= a; ++i)
{
for (j=0; j<100 && i*j <= a; ++j)
{
// ...
}
}
अन्य मामलों में आप विधि को 2 विधियों में विभाजित कर सकते हैं:
// Outer loop in method 1:
for (i=0; i<100 && processInner(i); ++i)
{
}
private bool processInner(int i)
{
int j;
for (j=0; j<100 && i*j <= a; ++j)
{
// ...
}
return i*j<=a;
}
अस्थायी चर? अच्छा, बुरा या बदसूरत?
आप लूप के भीतर से एक बूलियन वापस करने का फैसला कर सकते हैं (लेकिन मैं व्यक्तिगत रूप से SESE फॉर्म को पसंद करता हूं, क्योंकि कंपाइलर इसे कैसे देखेगा और मुझे लगता है कि यह पढ़ने के लिए क्लीनर है)।
कुछ लोग सोचते हैं कि यह एक अस्थायी चर का उपयोग करने के लिए क्लीनर है, और इस तरह से एक समाधान का प्रस्ताव करें:
bool more = true;
for (int i=0; i<100; ++i)
{
for (int j=0; j<100; ++j)
{
// ...
if (i*j > a) { more = false; break; } // yuck.
// ...
}
if (!more) { break; } // yuck.
// ...
}
// ...
मैं व्यक्तिगत रूप से इस दृष्टिकोण का विरोध कर रहा हूं। फिर से देखें कि कोड कैसे संकलित किया गया है। अब इन अच्छे, प्रेडिक्टेबल पैटर्न के साथ क्या करेंगे, इसके बारे में सोचें। तस्वीर ले आओ?
बेनाम: ठीक है, मुझे इसे बाहर जादू। क्या होगा:
- संकलक सब कुछ शाखाओं के रूप में लिख देगा।
- ऑप्टिमाइज़ेशन स्टेप के रूप में, कंपाइलर अजीब
more
चर को हटाने के प्रयास में डेटा फ्लो विश्लेषण करेगा जो केवल नियंत्रण प्रवाह में उपयोग होने के लिए होता है।
- यदि सक्सेज़फुल है, तो
more
प्रोग्राम से वैरिएबल को खत्म कर दिया जाएगा, और केवल ब्रांच ही रहेंगे। इन शाखाओं को अनुकूलित किया जाएगा, इसलिए आपको आंतरिक लूप से केवल एक ही शाखा मिलेगी।
- यदि अनुपयोगी है, तो
more
निश्चित रूप से आंतरिक-सबसे अधिक लूप में चर का उपयोग किया जाता है, इसलिए यदि कंपाइलर इसे अनुकूलित नहीं करेगा, तो यह एक रजिस्टर को आवंटित करने का एक उच्च मौका है (जो मूल्यवान रजिस्टर मेमोरी खाता है)।
इसलिए, संक्षेप करने के लिए: आपके कंपाइलर में ऑप्टिमाइज़र को यह पता लगाने के लिए बहुत परेशानी का एक नरक में जाना होगा कि more
इसका उपयोग केवल नियंत्रण प्रवाह के लिए किया जाता है, और सबसे अच्छी स्थिति में यह बाहरी के बाहर एक एकल शाखा में अनुवाद करेगा। पाश।
दूसरे शब्दों में, सबसे अच्छा मामला यह है कि यह इसके बराबर होगा:
for (int i=0; i<100; ++i)
{
for (int j=0; j<100; ++j)
{
// ...
if (i*j > a) { goto exitLoop; } // perhaps add a comment
// ...
}
// ...
}
exitLoop:
// ...
इस पर मेरी व्यक्तिगत राय काफी सरल है: अगर यही हम सभी के साथ करना चाहते हैं, तो चलिए संकलक और पठनीयता दोनों के लिए दुनिया को आसान बनाते हैं, और इसे तुरंत लिखते हैं।
tl; डॉ:
जमीनी स्तर:
- यदि संभव हो तो लूप के लिए अपनी साधारण स्थिति का उपयोग करें। उच्च स्तर की भाषा के निर्माणों से चिपके रहें जो आपके पास जितना संभव हो सके।
- यदि सब कुछ विफल हो जाता है और आप
goto
या तो साथ छोड़ दिए जाते हैं या bool more
पूर्व को प्राथमिकता देते हैं।