Rust, Haskell, C ++, Python, Scala और OCaml में समान प्रोजेक्ट की तुलना करना

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

हमने अपना रस्ट कंपाइलर बनाया, और पहले मैंने हास्केल टीम प्रोजेक्ट के साथ इसकी तुलना की। मुझे उम्मीद है कि उनका कार्यक्रम बहुत छोटा होगा, लेकिन यह एक ही आकार या बड़ा हो गया। वही OCaml के लिए जाता है। तब मैंने इसकी तुलना C ++ कंपाइलर के साथ की थी, और यह काफी अपेक्षित था कि कंपाइलर लगभग 30% बड़ा था, जिसका मुख्य कारण हेडर, योग प्रकारों की कमी और पैटर्न मिलान है। निम्नलिखित तुलना मेरी प्रेमिका के साथ थी, जिसने मेटाप्रोग्रामिंग और गतिशील प्रकारों की शक्ति के कारण पायथन में अपने दम पर कंपाइलर बनाया और हमारी तुलना में आधे से भी कम कोड का उपयोग किया। एक अन्य मित्र के पास एक छोटा सा स्काला कार्यक्रम था। जिस चीज ने मुझे सबसे ज्यादा चौंकाया, वह थी एक और टीम के साथ तुलना, जिसने रस्ट का भी इस्तेमाल किया, लेकिन वे अलग-अलग डिजाइन के फैसलों के कारण तीन गुना ज्यादा कोड वाली थीं। अंत में, कोड की मात्रा में सबसे बड़ा अंतर उसी भाषा के भीतर था!

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

सामग्री


  • मुझे यह सार्थक क्यों लगता है
  • जंग (तुलना के लिए आधार)
  • हास्केल : 1.0-1.6 आकार, आप कैसे गिनते हैं, दिलचस्प कारणों के आधार पर
  • सी ++ : 1.4 स्पष्ट कारणों के लिए आकार
  • अजगर : फैंसी मेटाप्रोग्रामिंग के कारण 0.5 आकार!
  • जंग (एक अन्य समूह) : एक अलग डिजाइन के कारण तीन गुना आकार!
  • स्केल : 0.7 आकार
  • OCaml : 1.0-1.6 का आकार इस बात पर निर्भर करता है कि आप हास्केल के समान कैसे गिनते हैं

मुझे यह सार्थक क्यों लगता है


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

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

इस प्रकार, मुझे लगता है कि कोड की मात्रा इस बात की एक अच्छी समझ प्रदान करती है कि प्रत्येक परियोजना को समर्थन देने के लिए कितना प्रयास करना होगा, यदि यह दीर्घकालिक था। मुझे लगता है कि परियोजनाओं के बीच बहुत अधिक अंतर भी आपको कुछ असाधारण बयानों का खंडन करने की अनुमति नहीं देता है, जो मैंने पढ़ा है, उदाहरण के लिए, कि हास्केल संकलक भाषा के कारण C ++ के आधे से अधिक आकार का होगा।

जंग (तुलना के लिए आधार)


मैंने और मेरे एक साथी ने पहले रस्ट में 10k से अधिक लाइनें लिखीं और तीसरे सहकर्मी ने लिखा, शायद, कुछ हैकाथॉन में 500 लाइनें। हमारी संकलक 6806 पंक्तियों में wc -l , 5900 लाइनों के स्रोत (रिक्त स्थान और टिप्पणियों के बिना), और 220 KB wc -c

मैंने पाया कि अन्य परियोजनाओं में कुछ अनुपातों के साथ इन अनुपातों का सम्मान किया जाता है, जिन्हें मैं नोट करूंगा। बाकी लेख के लिए, जब मैं स्ट्रिंग्स या रकम का संदर्भ देता हूं, तो मेरा मतलब है wc -l , लेकिन यह कोई फर्क नहीं पड़ता (जब तक कि मुझे अंतर नोटिस न हो), और आप एक गुणांक के साथ परिवर्तित कर सकते हैं।

मैंने हमारे डिजाइन का वर्णन करते हुए एक और लेख लिखा, जिसमें सभी सार्वजनिक और गुप्त परीक्षण पास किए गए। इसमें कुछ अतिरिक्त विशेषताएं भी शामिल हैं, जो हमने मौज-मस्ती के लिए किए, न कि पासिंग टेस्ट के लिए, जिसमें संभवत: लगभग 400 लाइनें जोड़ी गईं। इसमें हमारी यूनिट परीक्षणों की लगभग 500 लाइनें भी हैं।

हास्केल


हास्केल टीम में मेरे दो दोस्त शामिल थे, जिन्होंने संभवतः हास्केल की एक-दो हजार पंक्तियाँ लिखी थीं, साथ ही हास्केल और अन्य समान कार्यात्मक भाषाओं जैसे कि ओक्सक्ल और लीन के बारे में बहुत सारी ऑनलाइन सामग्री पढ़ी। उनके पास एक और टीममेट था जिसे मैं बहुत अच्छी तरह से नहीं जानता था, लेकिन ऐसा लगता है कि एक मजबूत प्रोग्रामर ने हास्केल को पहले इस्तेमाल किया था।

उनके संकलक में wc -l , 357 KB और कोड की 7777 लाइनें (SLOC) की कुल 9,750 लाइनें थीं। इस टीम में इन अनुपातों के बीच एकमात्र महत्वपूर्ण अंतर भी है: इनकी संकलक पंक्तियों में हमारी तुलना में 1.4 गुना बड़ी है, SLOC में 1.3 गुना और बाइट्स में 1.6 गुना है। उन्होंने किसी भी अतिरिक्त कार्यों को लागू नहीं किया, सार्वजनिक और गुप्त परीक्षणों का 100% पारित किया।

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

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

मेरे एक साथी के साथ रम करने के बाद, हम इस अंतर के लिए निम्नलिखित स्पष्टीकरण लेकर आए:

  • हमने एक हस्तलिखित लेक्सिकल विश्लेषक और एक पुनरावर्ती वंश विधि का उपयोग किया, और उन्होंने एक एनएफए और डीएफए जनरेटर और एक एलआर पार्सर का उपयोग किया , और फिर पार्सिंग ट्री को एएसटी ( सार सिंटैक्स ट्री , कोड का अधिक सुविधाजनक प्रतिनिधित्व) में बदलने के लिए एक पास किया। इसने उन्हें काफी अधिक कोड दिया: हमारे 1705 की तुलना में 2677 लाइनें, लगभग 1000 लाइनें अधिक।
  • उन्होंने काल्पनिक जेनेरिक एएसटी का उपयोग किया, जो विभिन्न प्रकार के मापदंडों पर चले गए क्योंकि प्रत्येक पास में अधिक जानकारी जोड़ी गई थी। पुनर्लेखन के लिए यह और अधिक सहायक कार्य संभवतः यह समझाते हैं कि उनका एएसटी कोड हमारे कार्यान्वयन से लगभग 500 लाइनों से अधिक लंबा क्यों है, जहां हम संरचनात्मक शाब्दिक और उत्परिवर्ती Option<_> क्षेत्रों को इकट्ठा करते हैं ताकि हम सूचनाओं को जोड़ सकें।
  • उनके पास अभी भी पीढ़ी के दौरान कोड की लगभग 400 लाइनें हैं, जो मुख्य रूप से शुद्ध रूप से कार्यात्मक तरीके से कोड को उत्पन्न करने और संयोजित करने के लिए आवश्यक अधिक से अधिक अमूर्तता से जुड़ी हैं, जहां हम केवल उत्परिवर्तन और लाइन लेखन का उपयोग करते हैं।

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

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

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

यह भी ध्यान रखना दिलचस्प है कि प्रत्येक परियोजना की शुरुआत में, प्रोफेसर का कहना है कि छात्र किसी भी भाषा का उपयोग कर सकते हैं जो विश्वविद्यालय के सर्वर पर चलती है, लेकिन चेतावनी देती है कि हास्केल में टीमें बाकी हिस्सों से अलग हैं: उनके पास ग्रेड में सबसे बड़ा बिखराव है। बहुत से लोग अपनी क्षमताओं को नजरअंदाज करते हैं और हास्केल टीमों में सबसे खराब ग्रेड हैं, हालांकि अन्य मेरे दोस्तों की तरह ही ठीक हैं।

सी ++


फिर मैंने C ++ टीम से अपने दोस्त से बात की। मैं इस टीम में केवल एक व्यक्ति को जानता था, लेकिन हमारे विश्वविद्यालय में कई पाठ्यक्रमों में C ++ का उपयोग किया जाता है, इसलिए शायद टीम में सभी को C ++ का अनुभव था।

उनकी परियोजना में 8733 लाइनें और 280 KB शामिल थे, न कि परीक्षण कोड सहित, बल्कि अतिरिक्त कार्यों की लगभग 500 लाइनें भी शामिल थीं। जो बिना परीक्षणों के हमारे कोड से 1.4 गुना बड़ा है, जिसमें अतिरिक्त कार्यों की लगभग 500 लाइनें भी हैं। उन्होंने 100% सार्वजनिक परीक्षण पास किए, लेकिन केवल 90% गुप्त परीक्षण। शायद इसलिए कि उन्होंने विनिर्देश द्वारा आवश्यक फैंसी वीटीबी सरणियों को लागू नहीं किया, जो कोड की शायद 50-100 पंक्तियां लेते हैं।

मैं आकार में इन मतभेदों में बहुत गहराई से नहीं आया। मुझे लगता है कि यह मुख्य रूप से है:

  • वे पुनरावर्ती वंश विधि के बजाय LR पार्सर और ट्री राइटर का उपयोग करते हैं।
  • C ++ में योग प्रकार और पैटर्न की तुलना की कमी, जिसका हमने व्यापक रूप से उपयोग किया है और जो बहुत उपयोगी थे।
  • हेडर फ़ाइलों में सभी हस्ताक्षरों की नकल करने की आवश्यकता है, जो कि जंग में नहीं है।

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

अजगर


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

उसके संकलक में 4581 लाइनें शामिल थीं और सभी सार्वजनिक और गुप्त परीक्षण पास किए। उसने किसी भी अन्य कमांड की तुलना में अधिक उन्नत कार्यों को लागू किया, लेकिन यह निर्धारित करना मुश्किल है कि कितना अतिरिक्त कोड लिया गया, क्योंकि कई अतिरिक्त फ़ंक्शन सरल चीजों के अधिक शक्तिशाली संस्करण थे जिन्हें सभी को लागू करने की आवश्यकता थी, जैसे कि फोल्डिंग कांस्टेंट और कोड उत्पन्न करना। अतिरिक्त कार्य संभवतः 1000-2000 पंक्तियां हैं, कम से कम, इसलिए मुझे यकीन है कि उसका कोड हमारे जैसे ही अभिव्यंजक से कम से कम दोगुना है।

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

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

visit.rs और डायनेमिक टाइपिंग की शक्ति का एक और उदाहरण 400-लाइन का visit.rs फ़ाइल, जो मूल रूप से एक दोहराई जाने वाली बॉयलरप्लेट कोड है जो एएसटी संरचनाओं के एक समूह पर एक आगंतुक को लागू करता है। पायथन में, यह लगभग 10 लाइनों का एक छोटा कार्य हो सकता है जो एएसटी नोड के क्षेत्रों को __dict__ और उन्हें दौरा करता है ( __dict__ विशेषता का उपयोग करके)।

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

जंग (एक और समूह)


मेरे लिए सबसे दिलचस्प तुलना मेरे दोस्त के साथ थी, जो एक टीम के साथी (जिसे मैं नहीं जानता था) के साथ रस्ट में एक प्रोजेक्ट कर रहा था। मेरे दोस्त को अच्छा Rust का अनुभव था। उन्होंने रस्ट कंपाइलर के विकास में योगदान दिया और बहुत कुछ पढ़ा। मुझे उनके कॉमरेड के बारे में कुछ भी पता नहीं है।

उनकी परियोजना में 17,211 कच्ची लाइनें, 15k स्रोत लाइनें और 637 KB शामिल थे, परीक्षण कोड और उत्पन्न कोड शामिल नहीं थे। इसका कोई अतिरिक्त कार्य नहीं था, और इसने 10 10 गुप्त परीक्षण और कोड जनरेशन के लिए 90% सार्वजनिक परीक्षण पास किए, क्योंकि विनिर्देश के अधिक विचित्र भागों को लागू करने की समय सीमा से पहले उनके पास पर्याप्त समय नहीं था। उनका कार्यक्रम हमारी भाषा से तीन गुना बड़ा है, जो एक ही भाषा में लिखा गया है, और कम कार्यक्षमता के साथ!

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

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

  • उन्होंने एक मानक, समान, स्ट्रिंग-आधारित पार्सिंग ट्री के बजाय पूरी तरह से टाइप किए गए पार्सिंग पेड़ का उपयोग करने का निर्णय लिया। यह संभवतः पार्सिंग चरण या अधिक जटिल पार्सर में बहुत अधिक प्रकार की परिभाषाओं और अतिरिक्त रूपांतरण कोड की आवश्यकता है।
  • उन्होंने पार्सिंग ट्री प्रकारों और एएसटी प्रकारों के बीच कनवर्ट करने के लिए उन्हें सत्यापित करने के लिए ट्रायफ्रोम tryfrom कार्यान्वयन का उपयोग किया। यह कई 10-20-लाइन impl ब्लॉक की ओर जाता है। ऐसा करने के लिए, हमने उन कार्यों का उपयोग किया जो Result प्रकार लौटाते हैं, जो कम लाइनें उत्पन्न करता है, और हमें टाइप संरचना से थोड़ा सा मुक्त करता है, पैरामीटराइजेशन और पुन: उपयोग को सरल करता है। हमारे लिए कुछ चीजें, जो सिंगल-लाइन match शाखाएं थीं, उनके पास 10-लाइन वाले ब्लॉक थे।
  • कॉपी-पेस्ट को कम करने के लिए हमारे प्रकार संरचित हैं। उदाहरण के लिए, उन्होंने अलग-अलग फ़ील्ड्स का उपयोग किया है is_abstract , is_native और is_static , जहां बाधा चेकिंग कोड को दो बार कॉपी किया जाना था: एक बार शून्य-टाइप किए गए तरीकों के लिए और एक बार मामूली संशोधनों के साथ वापसी प्रकार वाले तरीकों के लिए। जबकि हमारा void केवल एक विशेष प्रकार था, हम mode और visibility साथ संशोधक की एक वर्गीकरण के साथ आए थे, जिसने टाइप-स्तरीय बाधाओं को लागू किया था, और मैच ऑपरेटर के लिए डिफ़ॉल्ट रूप से बाधा त्रुटियों को उत्पन्न किया गया था, जो संशोधक सेट को mode और visibility में अनुवाद करता है।

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

कोड पीढ़ी के लिए उनके हिस्से में 3594 लाइनें और हमारी - 1560 शामिल हैं। मैंने उनके कोड को देखा और ऐसा लगता है कि लगभग सभी अंतर यह है कि उन्होंने कोडांतरक निर्देशों के लिए एक मध्यवर्ती डेटा संरचना को चुना, जहां हमने सीधे कोडांतरक आउटपुट के लिए स्ट्रिंग प्रारूपण का उपयोग किया था । उन्हें सभी उपयोग किए गए निर्देशों और ऑपरेंड प्रकारों के प्रकार और आउटपुट कार्यों को परिभाषित करना था। इसका अर्थ यह भी था कि विधानसभा के निर्देशों के निर्माण में अधिक कोड लगे। जहां हमारे पास लघु निर्देशों के साथ एक प्रारूप ऑपरेटर था, जैसे कि mov ecx, [edx] , उन्हें एक विशालकाय rustfmt ऑपरेटर की आवश्यकता थी, जिसे 6 लाइनों में विभाजित किया गया था, जिसने rustfmt के लिए मध्यवर्ती नेस्टेड प्रकारों के एक समूह के साथ एक निर्देश बनाया था जिसमें नेस्टेड ब्रैकेट्स के 6 स्तर शामिल हैं। हम संबंधित निर्देशों के ब्लॉक भी कर सकते हैं, जैसे कि एक समारोह में प्रस्तावना, एक प्रारूप प्रारूप में, जहां उन्हें प्रत्येक निर्देश के लिए पूर्ण निर्माण निर्दिष्ट करना था।

हमारी टीम इस तरह के अमूर्त का उपयोग करने पर विचार कर रही थी। टेक्स्ट असेंबली या सीधे इश्यू मशीन कोड को आउटपुट करने में सक्षम होना आसान था, हालांकि यह पाठ्यक्रम की आवश्यकता नहीं थी। एक ही चीज को कम कोड और बेहतर प्रदर्शन के साथ X86Writer X86Writer का उपयोग करके push(reg: Register) जैसी विधियों के साथ किया जा सकता है। हमने यह भी ध्यान में रखा कि यह डिबगिंग और परीक्षण को सरल बना सकता है, लेकिन हमने महसूस किया कि जेनरेट किए गए टेक्स्ट असेम्बलर को देखना वास्तव में स्नैपशॉट परीक्षण का उपयोग करके पढ़ना और परीक्षण करना अधिक आसान है, यदि आप उदारतापूर्वक टिप्पणियां डालते हैं। लेकिन हमने (स्पष्ट रूप से सही तरीके से) भविष्यवाणी की कि यह बहुत अधिक अतिरिक्त कोड ले जाएगा, और हमारी वास्तविक जरूरतों को देखते हुए, कोई वास्तविक लाभ नहीं था, इसलिए हमने चिंता नहीं की।

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

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

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

क्या वास्तव में मुझे लगता है कि अमूर्त की लागत है। सार भविष्य के विस्तार की सुविधा प्रदान कर सकते हैं या कुछ प्रकार की त्रुटियों से रक्षा कर सकते हैं, लेकिन उन्हें ध्यान में रखा जाना चाहिए, यह देखते हुए कि आपको समझने और फिर से तैयार करने के लिए तीन गुना अधिक कोड मिल सकता है, त्रुटियों के लिए तीन संभव स्थानों के रूप में तीन बार और परीक्षण के लिए कम समय और आगे विकास। हमारा प्रशिक्षण पाठ्यक्रम वास्तविक दुनिया से अलग था: हम यह सुनिश्चित करने के लिए जानते थे कि हम विकास के बाद कोड को कभी नहीं छूएंगे, इससे प्रोएक्टिव एब्सट्रैक्शन के लाभ समाप्त हो जाते हैं। हालांकि, अगर मुझे यह चुनना था कि कौन सा संकलक एक मनमाने ढंग से कार्य करने के साथ विस्तार करने के लिए जिसे आप बाद में कहेंगे, तो हम अपना चयन करेंगे, इसके साथ मेरी परिचितता पर भी विचार किए बिना। सिर्फ इसलिए कि इसे समझने के लिए बहुत कम कोड है, और मैं संभावित रूप से आवश्यकताओं के लिए सबसे अच्छा अमूर्त चुन सकता हूं (उदा।C ++ कमांड का मध्यवर्ती प्रतिनिधित्व) जब मैं विशिष्ट आवश्यकताओं को जानता हूं।

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

स्काला


मैंने एक दोस्त के साथ भी बात की, जिसने पिछले सेमेस्टर में स्काला पर एक प्रोजेक्ट किया था, लेकिन प्रोजेक्ट और परीक्षण बिल्कुल एक जैसे थे। उनके संकलक में 4141 लाइनें और ~ 160 KB कोड शामिल थे, परीक्षणों की गिनती नहीं। उन्होंने 10 गुप्त परीक्षणों और 100% खुले परीक्षणों में से 8 पास किए और किसी भी अतिरिक्त कार्य को लागू नहीं किया। इस प्रकार, अतिरिक्त कार्यों और परीक्षणों के बिना हमारी 5906 लाइनों की तुलना में, उनका संकलक 30% कम है।

छोटे डिजाइन कारकों में से एक पार्सिंग के लिए एक अलग दृष्टिकोण था। कोर्स ने LR टेबल जनरेटर के लिए कमांड लाइन टूल के उपयोग की अनुमति दी। इस टीम को छोड़कर किसी ने भी इसका इस्तेमाल नहीं किया। इसने उन्हें LR टेबल जनरेटर को लागू करने से बचाया। वे 150-पंक्ति पायथन लिपि के साथ LR व्याकरण लिखने से बचने में भी कामयाब रहे जिन्होंने जावा व्याकरण वेबपेज को इंटरनेट पर पाया और इसे जनरेटर इनपुट प्रारूप में अनुवादित किया। उन्हें अभी भी स्काला में किसी प्रकार का पेड़ बनाने की आवश्यकता थी, लेकिन सामान्य तौर पर हमारे 1443 की तुलना में पार्सिंग चरण में 1073 लाइनों की मात्रा थी, हालांकि यहां हमारे ढाल वंश पद्धति ने अन्य सभी टीमों की तुलना में मात्रा में लाभ दिया।

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

OCaml


चूंकि हमारी टीम के सभी सदस्य जेन स्ट्रीट (प्रौद्योगिकी ट्रेडिंग कंपनी - लगभग।) में इंटर्नशिप से गुजरते हैं , इसलिए मुझे अन्य पूर्व जेन स्ट्रीट इंटर्न के परिणाम को देखने में विशेष रूप से दिलचस्पी थी जिन्होंने कंपाइलर लिखने के लिए ओकेमेल को चुना।

उनका संकलक 10,914 लाइनें और 377 KB था, जिसमें थोड़ी मात्रा में परीक्षण कोड और कोई अतिरिक्त सुविधाएँ नहीं थीं। उन्होंने 9/10 गुप्त परीक्षण और सभी सार्वजनिक परीक्षण पास किए।

अन्य समूहों की तरह, ऐसा लगता है कि आकार में मुख्य अंतर एलआर पार्सर और पार्सिंग के लिए पेड़ के पुनर्लेखन के साथ-साथ रेक्सक्स-> एनएफए-> डीएफए रूपांतरण पाइपलाइन के लिए शाब्दिक विश्लेषण के लिए उपयोग करने के कारण है। बाइट के लिए समान अनुपात के साथ उनका फ्रंटेंड (लेक्सिकल विश्लेषण, पार्सिंग, एएसटी निर्माण) 5548 लाइनें और हमारा - 2164 है। उन्होंने इस अपेक्षा के साथ अपने पार्सर के लिए परीक्षण का भी उपयोग किया कि यह हमारे स्नैपशॉट परीक्षणों के समान था, जो अपेक्षित आउटपुट को कोड के बाहर रखते थे, इसलिए उनके पार्सर परीक्षणों ने कुल ~ 600 लाइनें बनाईं और हमारा - लगभग 200।

यह कंपाइलर के बाकी हिस्सों के लिए 5366 लाइनें (461 लाइनें जिनमें से प्रकार की घोषणाओं के साथ इंटरफ़ेस फ़ाइलें हैं) और हमारे लिए 4642 हैं, अंतर केवल 15% है, अगर हम उनकी इंटरफ़ेस फ़ाइलों की गणना करते हैं, और लगभग समान आकार, यदि हम गिनती कर रहे हैं। ऐसा लगता है कि हमारे पार्सिंग डिजाइन निर्णयों के अलावा, रस्ट और ओकेम्ल समान रूप से अभिव्यंजक प्रतीत होते हैं, सिवाय इसके कि ओमेक्एल को फ्रंट-एंड फ़ाइलों की आवश्यकता है, और रस्ट नहीं करता है।

निष्कर्ष


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

Source: https://habr.com/ru/post/hi456638/


All Articles