अद्यतन: मुझे यह प्रश्न इतना पसंद आया कि मैंने इसे 18 नवंबर 2011 को अपने ब्लॉग का विषय बनाया । महान प्रश्न के लिए धन्यवाद!
मैंने हमेशा सोचा है: स्टैक का उद्देश्य क्या है?
मेरा मानना है कि एमएसआईएल भाषा के मूल्यांकन स्टैक का मतलब है , न कि रनटाइम पर वास्तविक प्रति-थ्रेड स्टैक।
मेमोरी से स्टैक या "लोडिंग" में स्थानांतरण क्यों है? दूसरी ओर, स्टैक से मेमोरी या "स्टोरिंग" में स्थानांतरण क्यों है? सिर्फ उन सभी को स्मृति में क्यों नहीं रखा गया है?
MSIL एक "वर्चुअल मशीन" भाषा है। C # कंपाइलर जैसे कंपाइलर CIL जनरेट करते हैं , और फिर रनटाइम के दौरान JIT (जस्ट इन टाइम) कंपाइलर नामक एक अन्य कंपाइलर IL को वास्तविक मशीन कोड में बदल देता है जो निष्पादित कर सकता है।
तो पहले इस प्रश्न का उत्तर दें कि "MSIL आखिर क्यों है?" सिर्फ C # कंपाइलर को मशीन कोड क्यों नहीं लिखा जाना चाहिए?
क्योंकि यह इस तरह से करना सस्ता है। मान लीजिए हमने ऐसा नहीं किया; मान लें कि प्रत्येक भाषा का अपना मशीन कोड जनरेटर होना चाहिए। आपके पास बीस अलग-अलग भाषाएँ हैं: C #, JScript .NET , Visual Basic, IronPython , F # ... और मान लीजिए कि आपके पास दस अन्य प्रोसेसर हैं। आपको कितने कोड जनरेटर लिखने होंगे? 20 x 10 = 200 कोड जनरेटर। वह बहुत काम की चीज है। अब मान लीजिए कि आप एक नया प्रोसेसर जोड़ना चाहते हैं। आपको इसके लिए कोड जनरेटर को बीस बार लिखना होगा, प्रत्येक भाषा के लिए एक।
इसके अलावा, यह मुश्किल और खतरनाक काम है। चिप्स के लिए कुशल कोड जनरेटर लिखना जो आप एक विशेषज्ञ नहीं हैं, एक कठिन काम है! संकलक डिजाइनर अपनी भाषा के शब्दार्थ विश्लेषण पर विशेषज्ञ हैं, न कि नए चिप सेटों के कुशल रजिस्टर आवंटन पर।
अब मान लीजिए कि हम इसे CIL तरीके से करते हैं। आपको कितने CIL जनरेटर लिखने हैं? एक प्रति भाषा। आपको कितने JIT कंपाइलर लिखने हैं? एक प्रति प्रोसेसर। कुल: 20 + 10 = 30 कोड जनरेटर। इसके अलावा, भाषा से सीआईएल जनरेटर लिखना आसान है क्योंकि सीआईएल एक सरल भाषा है, और सीआईएल-टू-मशीन-कोड जनरेटर भी लिखना आसान है क्योंकि सीआईएल एक सरल भाषा है। हम C # और VB की सभी पेचीदगियों से छुटकारा पा लेते हैं और व्हाट्सएप और "लोअर" सब कुछ एक सरल भाषा में करते हैं जिसके लिए एक घबराना लिखना आसान है।
एक मध्यवर्ती भाषा होने से एक नई भाषा संकलक के उत्पादन की लागत नाटकीय रूप से कम हो जाती है । यह नाटकीय रूप से एक नई चिप का समर्थन करने की लागत को कम करता है। आप एक नई चिप का समर्थन करना चाहते हैं, आप उस चिप पर कुछ विशेषज्ञों को ढूंढते हैं और उन्हें एक सीआईएल घबराना लिखते हैं और आपका काम हो जाता है; फिर आप अपनी चिप पर उन सभी भाषाओं का समर्थन करते हैं।
ठीक है, इसलिए हमने स्थापित किया है कि हमारे पास एमएसआईएल क्यों है; क्योंकि एक मध्यवर्ती भाषा होने से लागत कम होती है। फिर भाषा "स्टैक मशीन" क्यों है?
क्योंकि स्टैक मशीन से निपटने के लिए भाषा संकलक लेखकों के लिए वैचारिक रूप से बहुत सरल हैं। ढेर संगणना का वर्णन करने के लिए एक सरल, आसानी से समझा जाने वाला तंत्र है। JIT कंपाइलर लेखकों से निपटने के लिए स्टैक मशीनें भी वैचारिक रूप से बहुत आसान हैं। स्टैक का उपयोग करना एक सरल अमूर्तता है, और इसलिए फिर से, यह हमारी लागत को कम करता है ।
आप पूछते हैं "आखिर एक स्टैक क्यों है?" क्यों नहीं सब कुछ सीधे स्मृति से बाहर है? खैर, चलिए इसके बारे में सोचते हैं। मान लीजिए कि आप CIL कोड जनरेट करना चाहते हैं:
int x = A() + B() + C() + 10;
मान लीजिए कि हमारे पास यह कन्वेंशन है कि "ऐड", "कॉल", "स्टोर" और इसी तरह हमेशा स्टैक से अपने तर्क लें और स्टैक पर अपना परिणाम (यदि कोई है) डाल दें। इस C # के लिए CIL कोड जनरेट करने के लिए हम ऐसा कुछ कहते हैं:
load the address of x // The stack now contains address of x
call A() // The stack contains address of x and result of A()
call B() // Address of x, result of A(), result of B()
add // Address of x, result of A() + B()
call C() // Address of x, result of A() + B(), result of C()
add // Address of x, result of A() + B() + C()
load 10 // Address of x, result of A() + B() + C(), 10
add // Address of x, result of A() + B() + C() + 10
store in address // The result is now stored in x, and the stack is empty.
अब मान लीजिए कि हमने इसे बिना स्टैक के किया। हम इसे आपके तरीके से करेंगे, जहाँ हर ओपकोड अपने ऑपरेंड्स के पते लेता है और जिस पते पर वह अपना रिजल्ट स्टोर करता है :
Allocate temporary store T1 for result of A()
Call A() with the address of T1
Allocate temporary store T2 for result of B()
Call B() with the address of T2
Allocate temporary store T3 for the result of the first addition
Add contents of T1 to T2, then store the result into the address of T3
Allocate temporary store T4 for the result of C()
Call C() with the address of T4
Allocate temporary store T5 for result of the second addition
...
आप देखें कि यह कैसे होता है? हमारा कोड बहुत बड़ा हो रहा है क्योंकि हमें सभी अस्थायी भंडारण को स्पष्ट रूप से आवंटित करना है जो कि आमतौर पर सम्मेलन द्वारा केवल स्टैक पर चलते हैं । इससे भी बुरी बात यह है कि हमारे ऑपकोड अपने आप में भारी होते जा रहे हैं क्योंकि इन सभी को अब एक तर्क के रूप में लेना होगा कि वे अपना परिणाम और प्रत्येक ऑपरेंड का पता लिखने जा रहे हैं। एक "ऐड" निर्देश जो जानता है कि यह स्टैक से दो चीजों को लेने जा रहा है और एक चीज़ को एक बाइट पर रखा जा सकता है। एक निर्देश जो दो ऑपरेंड पते लेता है और एक परिणाम पता बहुत बड़ा होने जा रहा है।
हम स्टैक-आधारित ऑपकोड का उपयोग करते हैं क्योंकि स्टैक सामान्य समस्या को हल करते हैं । अर्थात्: मैं कुछ अस्थायी भंडारण आवंटित करना चाहता हूं, बहुत जल्द इसका उपयोग करूंगा और जब मैं पूरा कर लूंगा, तो इससे जल्दी छुटकारा पा लूंगा । यह अनुमान लगाकर कि हमारे पास हमारे निपटान में एक स्टैक है, हम opcodes को बहुत छोटा बना सकते हैं और कोड बहुत ही जटिल है।
अद्यतन: कुछ अतिरिक्त विचार
संयोग से, (1) एक वर्चुअल मशीन, (2) लेखन कंपाइलर्स, जो VM भाषा को लक्षित करते हैं, और (3) विभिन्न प्रकार के हार्डवेयर पर VM का कार्यान्वयन लागू करते हैं, (1) को कम करने का यह विचार एक नया विचार नहीं है। । इसकी उत्पत्ति MSIL, LLVM, Java bytecode, या किसी अन्य आधुनिक अवसंरचना से नहीं हुई थी। इस रणनीति का सबसे पहला कार्यान्वयन मुझे पता है कि 1966 से पीसीओडी मशीन है।
इस अवधारणा के बारे में मैंने पहली बार व्यक्तिगत रूप से सुना था, जब मुझे पता चला कि इन्फोकॉम के कार्यान्वयनकर्ताओं ने इतने अलग-अलग मशीनों पर ज़र्क को इतनी अच्छी तरह से चलाने में कैसे कामयाब रहे। उन्होंने Z- मशीन नामक एक वर्चुअल मशीन को निर्दिष्ट किया और फिर उन सभी हार्डवेयरों के लिए Z- मशीन एमुलेटर बनाए जिन्हें वे अपने गेम को चलाना चाहते थे। इससे अतिरिक्त लाभ यह हुआ कि वे 8-बिट सिस्टम पर वर्चुअल मेमोरी प्रबंधन को लागू कर सकते थे ; एक गेम मेमोरी में फिट होने की तुलना में बड़ा हो सकता है क्योंकि वे डिस्क से कोड को केवल तब पेज कर सकते हैं जब उन्हें इसकी आवश्यकता होती है और जब वे नए कोड को लोड करने की आवश्यकता होती है तो इसे त्याग देते हैं।