इस प्रश्नोत्तर के लिए देर से आने वाला, पहले से ही शानदार उत्तरों के साथ, लेकिन मैं एक विदेशी के रूप में घुसपैठ करना चाहता था क्योंकि स्मृति में बिट्स और बाइट्स के निचले स्तर के दृष्टिकोण से चीजों को देखता था।
मैं एक सी नजरिए से आने वाले अपरिवर्तनीय डिजाइनों से बहुत उत्साहित हूं, और नए तरीकों को खोजने के नजरिए से इस जानवर के प्रभावी ढंग से प्रोग्राम करने के लिए हमारे पास इन दिनों है।
धीमी तेज़ /
इस सवाल के रूप में कि क्या यह चीजों को धीमा करता है, एक रोबोट का जवाब होगा yes
। इस तरह के बहुत तकनीकी वैचारिक स्तर पर, अपरिवर्तनीयता ही चीजों को धीमा बना सकती है। हार्डवेयर तब सबसे अच्छा करता है जब यह छिटपुट रूप से मेमोरी आवंटित नहीं करता है और इसके बजाय मौजूदा मेमोरी को संशोधित कर सकता है (क्यों हमारे पास टेम्पोरल लोकेलिटी जैसी अवधारणाएं हैं)।
फिर भी एक व्यावहारिक जवाब है maybe
। प्रदर्शन अभी भी काफी हद तक किसी भी गैर-तुच्छ कोडबेस में उत्पादकता मीट्रिक है। हम आमतौर पर भयावह-से-बनाए रखने वाले कोडबेस को रेस की परिस्थितियों में ट्रिपिंग नहीं करते हैं, भले ही हम बग्स की अवहेलना करते हों। दक्षता अक्सर लालित्य और सादगी का एक कार्य है। माइक्रो-ऑप्टिमाइज़ेशन का चरम कुछ हद तक संघर्ष कर सकता है, लेकिन वे आमतौर पर कोड के सबसे छोटे और सबसे महत्वपूर्ण वर्गों के लिए आरक्षित होते हैं।
अपरिवर्तनीय बिट्स और बाइट्स को बदलना
निम्न-स्तर के दृष्टिकोण से आ रहा है, अगर हम एक्स-रे अवधारणाओं को पसंद करते हैं objects
और strings
इसके आगे, इसके दिल में बस बिट्स और बाइट्स हैं जो विभिन्न रूपों में विभिन्न गति / आकार विशेषताओं (मेमोरी हार्डवेयर की गति और आकार) के साथ आम तौर पर होते हैं परस्पर अनन्य)।
कंप्यूटर का मेमोरी पदानुक्रम इसे पसंद करता है जब हम बार-बार मेमोरी के एक ही हिस्से तक पहुँचते हैं, जैसे कि ऊपर दिए गए आरेख में, क्योंकि यह मेमोरी के सबसे तेज़ी से एक्सेस किए गए चंक को मेमोरी के सबसे तेज़ रूप (एल 1 कैश), जैसे, में रखेगा, जो लगभग एक रजिस्टर के रूप में तेजी से है)। हम बार-बार सटीक एक ही मेमोरी (इसे कई बार पुन: उपयोग करते हुए) तक पहुंच सकते हैं या बार-बार चंक के विभिन्न वर्गों तक पहुंच सकते हैं (उदाहरण के लिए, एक सन्निहित चंक में तत्वों के माध्यम से लूपिंग जो बार-बार स्मृति के उस भाग के विभिन्न वर्गों तक पहुंचता है)।
हम अंत में उस प्रक्रिया में एक रिंच फेंकते हैं, यदि यह मेमोरी को संशोधित करता है तो पक्ष में एक नया मेमोरी ब्लॉक बनाना चाहता है, जैसे:
... इस मामले में, नए मेमोरी ब्लॉक तक पहुंचने के लिए अनिवार्य पेज दोष और कैश मिस की आवश्यकता हो सकती है ताकि इसे मेमोरी के सबसे तेज़ रूपों (सभी तरह रजिस्टर में) में वापस ले जाया जा सके। यह एक वास्तविक प्रदर्शन हत्यारा हो सकता है।
इसे कम करने के तरीके हैं, हालांकि, पहले से स्पर्श किए गए प्रचारित मेमोरी के आरक्षित पूल का उपयोग करते हुए।
बिग एग्रीगेट्स
एक और वैचारिक मुद्दा जो थोड़े उच्च स्तर के दृष्टिकोण से उत्पन्न होता है, वह वास्तव में थोक में वास्तव में बड़े समुच्चय की अनावश्यक प्रतियों का काम करता है।
एक अत्यधिक जटिल आरेख से बचने के लिए, आइए कल्पना करें कि यह सरल मेमोरी ब्लॉक किसी तरह महंगा था (शायद अविश्वसनीय सीमित हार्डवेयर पर UTF-32 वर्ण)।
इस मामले में, यदि हम "HELP" को "KILL" से बदलना चाहते थे और यह मेमोरी ब्लॉक अपरिवर्तनीय था, तो हमें एक अद्वितीय नई ऑब्जेक्ट बनाने के लिए इसकी संपूर्णता में एक नया ब्लॉक बनाना होगा, भले ही इसके कुछ भाग बदल गए हों :
हमारी कल्पना को थोड़ा सा बढ़ाते हुए, इस तरह की गहरी नकल बाकी सब कुछ सिर्फ एक छोटा सा हिस्सा बनाने के लिए काफी महंगी हो सकती है (वास्तविक दुनिया के मामलों में, यह मेमोरी ब्लॉक बहुत अधिक होगा, किसी समस्या को हल करने के लिए बहुत बड़ा)।
हालांकि, इस तरह के खर्च के बावजूद, इस तरह की डिजाइन मानव त्रुटि से काफी कम प्रवृत्त होगी। शुद्ध कार्यों के साथ एक कार्यात्मक भाषा में काम करने वाला कोई भी व्यक्ति इसकी सराहना कर सकता है, और विशेष रूप से बहुपरत मामलों में जहां हम दुनिया में देखभाल के बिना इस तरह के कोड को गुणा कर सकते हैं। सामान्य तौर पर, मानव प्रोग्रामर राज्य परिवर्तनों पर यात्रा करते हैं, विशेष रूप से वे जो वर्तमान फ़ंक्शन के दायरे के बाहर राज्यों को बाहरी दुष्प्रभाव का कारण बनते हैं। यहां तक कि इस तरह के मामले में बाहरी त्रुटि (अपवाद) से उबरना मिश्रण में उत्परिवर्तनीय बाहरी राज्य परिवर्तनों के साथ अविश्वसनीय रूप से मुश्किल हो सकता है।
इस निरर्थक नकल के काम को कम करने का एक तरीका यह है कि इन मेमोरी ब्लॉक्स को वर्णों (या संदर्भों) के संग्रह में पात्रों के रूप में बनाया जाए:
क्षमायाचना, मैं यह महसूस करने में विफल रहा L
कि आरेख बनाते समय हमें अद्वितीय बनाने की आवश्यकता नहीं है ।
ब्लू उथले कॉपी किए गए डेटा को इंगित करता है।
... दुर्भाग्य से, यह प्रति चरित्र सूचक / संदर्भ लागत का भुगतान करने के लिए अविश्वसनीय रूप से महंगा होगा। इसके अलावा, हम पता स्थान पर सभी वर्णों की सामग्री को बिखेर सकते हैं और पृष्ठ दोष और कैश मिस के बोट लोड के रूप में इसके लिए भुगतान कर सकते हैं, आसानी से इस समाधान को पूरी तरह से अपनी संपूर्णता में कॉपी करने से भी बदतर बना सकते हैं।
यहां तक कि अगर हम सावधानी से इन पात्रों को आवंटित करने के लिए सावधान थे, तो कहो कि मशीन 8 वर्णों और 8 बिंदुओं को एक चरित्र को कैश लाइन में लोड कर सकती है। हम नई स्ट्रिंग को पार करने के लिए इस तरह से लोडिंग मेमोरी को समाप्त करते हैं:
इस मामले में, हम इस स्ट्रिंग को पार करने के लिए सन्निहित स्मृति के लायक 7 अलग कैश लाइनों की आवश्यकता को समाप्त करते हैं, जब आदर्श रूप से हमें केवल 3 की आवश्यकता होती है।
डेटा का हिस्सा
उपरोक्त समस्या को कम करने के लिए, हम एक ही मूल रणनीति को लागू कर सकते हैं, लेकिन 8 वर्णों के मोटे स्तर पर, जैसे
परिणाम को 4 स्ट्रिंग लाइनों की आवश्यकता होती है, जिसमें डेटा (3 पॉइंटर्स के लिए 1, और पात्रों के लिए 3) इस स्ट्रिंग को पार करने के लिए लोड किया जाना चाहिए जो सैद्धांतिक इष्टतम का केवल 1 छोटा है।
तो यह बिल्कुल भी बुरा नहीं है। कुछ मेमोरी बेकार है, लेकिन मेमोरी भरपूर है और यदि अतिरिक्त मेमोरी को अक्सर एक्सेस नहीं किया जा रहा है तो अतिरिक्त मेमोरी का उपयोग करने से चीजें धीमी नहीं होती हैं। यह केवल गर्म, सन्निहित डेटा के लिए है जहाँ मेमोरी का उपयोग कम हो जाता है और गति अक्सर हाथ से हाथ जाती है जहाँ हम अधिक मेमोरी को एक पेज या कैश लाइन में फिट करना चाहते हैं और बेदखली से पहले सभी तक पहुँच सकते हैं। यह प्रतिनिधित्व काफी कैश-फ्रेंडली है।
गति
इसलिए ऊपर की तरह एक प्रतिनिधित्व का उपयोग प्रदर्शन का काफी सभ्य संतुलन दे सकता है। संभवतया अपरिवर्तनीय डेटा संरचनाओं के सबसे अधिक प्रदर्शन-महत्वपूर्ण उपयोग डेटा के चंकी टुकड़ों को संशोधित करने और उन्हें प्रक्रिया में अद्वितीय बनाने की इस प्रकृति पर ले जाएंगे, जबकि उथले अनमॉडिफाइड टुकड़ों की नकल करते हैं। यह परमाणु संचालन के कुछ ओवरहेड को एक बहुआयामी संदर्भ में सुरक्षित रूप से उथले कॉपी किए गए टुकड़ों को संदर्भित करने के लिए भी करता है (संभवतः कुछ परमाणु संदर्भ-गिनती के साथ चल रहा है)।
फिर भी जब तक डेटा के इन चंकी टुकड़ों का पर्याप्त स्तर पर प्रतिनिधित्व किया जाता है, तब तक इस ओवरहेड का बहुत कुछ कम हो जाता है और संभवतः यह भी तुच्छ हो जाता है, जबकि अभी भी हमें बाहरी पक्ष के बिना शुद्ध रूप में अधिक फ़ंक्शन कोडिंग और मल्टीथ्रेडिंग की सुरक्षा और सुविधा प्रदान करता है। प्रभाव।
नया और पुराना डाटा रखना
जहाँ मैं प्रदर्शन के दृष्टिकोण से संभावित रूप से सबसे अधिक सहायक (व्यावहारिक अर्थ में) के रूप में अपरिवर्तनीयता को देखता हूं, जब हमें बड़े डेटा की संपूर्ण प्रतियां बनाने के लिए प्रलोभन दिया जा सकता है ताकि यह एक परस्पर संदर्भ में अद्वितीय बना सके जहां लक्ष्य कुछ नया उत्पादन करना है कुछ ऐसा जो पहले से ही मौजूद है, जहाँ हम नए और पुराने दोनों को रखना चाहते हैं, जब हम सावधानीपूर्वक अनुपयोगी डिज़ाइन के साथ थोड़े से टुकड़े और उसके टुकड़े बना सकते हैं।
उदाहरण: पूर्ववत करें
इसका एक उदाहरण पूर्ववत व्यवस्था है। हम एक डेटा संरचना के एक छोटे से हिस्से को बदल सकते हैं और दोनों मूल रूप को रखना चाहते हैं जिन्हें हम पूर्ववत कर सकते हैं, और नया रूप। इस तरह के अपरिवर्तनीय डिजाइन के साथ जो केवल डेटा संरचना के छोटे, संशोधित खंडों को अद्वितीय बनाता है, हम केवल पुराने डेटा की एक प्रतिलिपि को पूर्ववत प्रविष्टि में संग्रहीत कर सकते हैं, जबकि केवल जोड़े गए अद्वितीय भागों डेटा की स्मृति लागत का भुगतान कर सकते हैं। यह उत्पादकता का बहुत प्रभावी संतुलन प्रदान करता है (पूर्ववत व्यवस्था को केक का एक टुकड़ा बनाकर) और प्रदर्शन।
उच्च स्तरीय इंटरफेस
फिर भी उपरोक्त मामले के साथ कुछ अजीब है। एक स्थानीय प्रकार के फ़ंक्शन संदर्भ में, परिवर्तनशील डेटा अक्सर संशोधित करने के लिए सबसे आसान और सबसे सरल है। आखिरकार, किसी सरणी को संशोधित करने का सबसे आसान तरीका अक्सर इसके माध्यम से लूप करना है और एक समय में एक तत्व को संशोधित करना है। यदि हम किसी सरणी को बदलने के लिए चुनने के लिए उच्च-स्तरीय एल्गोरिदम की एक बड़ी संख्या रखते हैं, तो हम बौद्धिक ओवरहेड को समाप्त कर सकते हैं और यह सुनिश्चित करने के लिए उपयुक्त एक को चुनना होगा कि ये सभी चंकी उथली प्रतियां बनी हुई हैं, जबकि भागों को संशोधित किया गया है अद्वितीय बना दिया।
संभवतः उन मामलों में सबसे आसान तरीका एक फ़ंक्शन (जहां वे आमतौर पर हमें यात्रा नहीं करते हैं) के संदर्भ में स्थानीय रूप से म्यूटेबल बफ़र्स का उपयोग करना है, जो एक नई अपरिवर्तनीय प्रतिलिपि प्राप्त करने के लिए डेटा संरचना में एटोमिक रूप से परिवर्तन करते हैं (मुझे विश्वास है कि कुछ भाषाएं कॉल करती हैं ये "ग्राहक") ...
... या हम केवल डेटा पर उच्च और उच्च-स्तरीय ट्रांसफ़ॉर्म फ़ंक्शंस मॉडल कर सकते हैं ताकि हम एक म्यूटेबल बफर को संशोधित करने और उत्परिवर्तित तर्क के बिना संरचना में इसे करने की प्रक्रिया को छिपा सकें। किसी भी मामले में, यह अभी तक एक व्यापक रूप से खोजा गया क्षेत्र नहीं है, और इन डेटा संरचनाओं को कैसे बदलना है, इसके लिए सार्थक इंटरफेस के साथ आने के लिए हम अपरिवर्तनीय डिजाइनों को अपनाने के लिए अपने काम में कटौती करते हैं।
डेटा संरचनाएं
एक और बात जो यहां उठती है वह यह है कि प्रदर्शन-महत्वपूर्ण संदर्भ में उपयोग की जाने वाली अपरिवर्तनीयता संभवतः डेटा संरचनाओं को चंकी डेटा तक तोड़ना चाहती है जहां चंक आकार में बहुत छोटे नहीं हैं, लेकिन बहुत बड़े भी नहीं हैं।
लिंक की गई सूचियाँ इसे समायोजित करने और अनियंत्रित सूचियों में बदलने के लिए बहुत थोड़ा बदलना चाह सकती हैं। रैंडम एक्सेस के लिए मॉडुलो इंडेक्सिंग के साथ बड़े, सन्निहित सरणियों को सन्निहित खंडों में बदल दिया जा सकता है।
यह संभावित रूप से डेटा संरचनाओं को एक दिलचस्प तरीके से बदल देता है, जबकि इन डेटा संरचनाओं के संशोधित कार्यों को आगे बढ़ाते हुए उथले कुछ बिट्स को कॉपी करने और अन्य बिट्स को वहां अद्वितीय बनाने के लिए अतिरिक्त जटिलता को छिपाने के लिए एक थोक प्रकृति की तरह काम करता है।
प्रदर्शन
वैसे भी, इस विषय पर मेरा निम्न-स्तरीय दृष्टिकोण है। सैद्धांतिक रूप से, अपरिवर्तनीयता की लागत बहुत बड़ी से लेकर छोटी तक हो सकती है। लेकिन एक बहुत ही सैद्धांतिक दृष्टिकोण हमेशा अनुप्रयोगों को तेज नहीं बनाता है। यह उन्हें स्केलेबल बना सकता है, लेकिन वास्तविक दुनिया की गति को अक्सर अधिक व्यावहारिक मानसिकता को गले लगाने की आवश्यकता होती है।
व्यावहारिक दृष्टिकोण से, प्रदर्शन, स्थिरता और सुरक्षा जैसे गुण एक बड़े धब्बा में बदल जाते हैं, विशेष रूप से एक बहुत बड़े कोडबेस के लिए। जबकि कुछ निरपेक्ष अर्थों में प्रदर्शन को अपरिवर्तनीयता के साथ अपमानित किया जाता है, यह उत्पादकता और सुरक्षा (थ्रेड-सुरक्षा सहित) पर होने वाले लाभों के बारे में बहस करना मुश्किल है। इनकी वृद्धि के साथ, अक्सर व्यावहारिक प्रदर्शन में वृद्धि हो सकती है, यदि केवल इसलिए कि डेवलपर्स के पास बगों के झुंड के बिना अपने कोड को ट्यून और अनुकूलित करने के लिए अधिक समय है।
इसलिए मुझे लगता है कि इस व्यावहारिक अर्थ से, अपरिवर्तनीय डेटा संरचनाएं वास्तव में बहुत सारे मामलों में प्रदर्शन की सहायता कर सकती हैं, जितना अजीब लगता है। एक आदर्श दुनिया इन दोनों के मिश्रण की तलाश कर सकती है: अपरिवर्तनीय डेटा संरचनाएं और उत्परिवर्तनीय, जिनके साथ उत्परिवर्तनीय वाले आमतौर पर बहुत स्थानीय दायरे में उपयोग करने के लिए सुरक्षित होते हैं (उदा: एक फ़ंक्शन के लिए स्थानीय), जबकि अपरिवर्तनीय बाहरी पक्ष से बच सकते हैं एकमुश्त प्रभाव डालते हैं और डेटा संरचना के सभी परिवर्तनों को एक परमाणु ऑपरेशन में बदल देते हैं, जो दौड़ की स्थिति का कोई जोखिम नहीं है।