मैं आपके कोडबेस (ईसीएस इंजन) के कुछ सबसे केंद्रीय हिस्सों का वर्णन करता हूं जो आपके द्वारा वर्णित डेटा संरचना के प्रकार के आसपास हैं, हालांकि यह छोटे सन्निहित ब्लॉक (4 मेगाबाइट के बजाय 4 किलोबाइट्स की तरह) का उपयोग करता है।
यह उस ब्लॉक के सूचकांकों के लिए ब्लॉक के अंदर डालने के लिए तैयार होने वाले ब्लॉक (जो ब्लॉक पूर्ण नहीं हैं) के लिए एक मुक्त सूची के साथ निरंतर-समय सम्मिलन और निष्कासन को प्राप्त करने के लिए एक डबल फ्री सूची का उपयोग करता है। प्रविष्टि पर पुनः प्राप्त होने के लिए तैयार है।
मैं इस संरचना के पेशेवरों और विपक्षों को कवर करूँगा। चलो कुछ विपक्ष के साथ शुरू करते हैं क्योंकि उनमें से कई हैं:
विपक्ष
- इस संरचना में
std::vector
(शुद्ध रूप से सन्निहित संरचना) की तुलना में कुछ सौ मिलियन तत्वों को सम्मिलित करने में लगभग 4 गुना अधिक समय लगता है । और मैं माइक्रो-ऑप्टिमाइज़ेशन में बहुत सभ्य हूं लेकिन सामान्य रूप से ऐसा करने के लिए सिर्फ वैचारिक रूप से अधिक काम करना है क्योंकि पहले ब्लॉक मुक्त सूची के शीर्ष पर मुक्त ब्लॉक का निरीक्षण करना है, फिर ब्लॉक का उपयोग करें और ब्लॉक से एक मुक्त सूचकांक पॉप करें फ्री लिस्ट, एलिमेंट को फ्री पोजीशन पर लिखें, और फिर चेक करें कि क्या ब्लॉक फुल है और ब्लॉक फ्री लिस्ट से ब्लॉक को पॉप करें या नहीं। यह अभी भी एक निरंतर-समय ऑपरेशन है, लेकिन पीछे की ओर धकेलने की तुलना में बहुत बड़ा स्थिरांक है std::vector
।
- अनुक्रमण के लिए अतिरिक्त अंकगणित और अप्रत्यक्ष की अतिरिक्त परत को देखते हुए यादृच्छिक-अभिगम पैटर्न का उपयोग करने वाले तत्वों तक पहुंचने में लगभग दोगुना समय लगता है।
- अनुक्रमिक पहुँच कुशलता से एक इटोमर डिज़ाइन में मैप नहीं होती है क्योंकि इट्रीमीटर को हर बार बढ़ने के बाद अतिरिक्त ब्रांचिंग करनी होती है।
- इसमें कुछ मेमोरी ओवरहेड है, आमतौर पर प्रति तत्व लगभग 1 बिट। 1 बिट प्रति तत्व अधिक ध्वनि नहीं हो सकती है, लेकिन यदि आप एक मिलियन 16-बिट पूर्णांक को संग्रहीत करने के लिए इसका उपयोग कर रहे हैं, तो यह 6.25% एक पूरी तरह से कॉम्पैक्ट सरणी की तुलना में अधिक मेमोरी उपयोग है। हालाँकि, व्यवहार में यह कम मेमोरी का उपयोग करने के लिए जाता है
std::vector
जब तक कि आप vector
उस अतिरिक्त क्षमता को समाप्त करने के लिए जमा नहीं कर रहे हैं जो इसे सुरक्षित रखता है। इसके अलावा मैं आमतौर पर इस तरह के नन्हा तत्वों को संग्रहीत करने के लिए इसका इस्तेमाल नहीं करता।
पेशेवरों
- एक
for_each
फ़ंक्शन का उपयोग करके अनुक्रमिक पहुंच जो एक ब्लॉक के भीतर तत्वों की कॉलबैक प्रोसेसिंग रेंज लेता है, लगभग std::vector
10% (जैसे केवल 10% अंतर) के साथ अनुक्रमिक पहुंच की गति को प्रतिद्वंद्वी करता है , इसलिए यह मेरे लिए सबसे अधिक प्रदर्शन-महत्वपूर्ण उपयोग मामलों में बहुत कम कुशल नहीं है ( एक ईसीएस इंजन में बिताया जाने वाला अधिकांश समय अनुक्रमिक पहुंच में है)।
- जब वे पूरी तरह से खाली हो जाते हैं, तो संरचना से निपटने वाले ब्लॉकों के बीच से निरंतर-समय के निष्कासन की अनुमति देता है। परिणामस्वरूप यह आम तौर पर यह सुनिश्चित करने में काफी सभ्य है कि डेटा संरचना कभी भी आवश्यकता से अधिक मेमोरी का उपयोग नहीं करती है।
- यह उन तत्वों के सूचकांकों को अमान्य नहीं करता है, जो कंटेनर से सीधे नहीं हटाए जाते हैं क्योंकि यह बाद में सम्मिलन पर उन छेदों को पुनः प्राप्त करने के लिए एक मुफ्त सूची दृष्टिकोण का उपयोग करके पीछे छोड़ देता है।
- आपको स्मृति से बाहर चलने के बारे में बहुत चिंता करने की ज़रूरत नहीं है, भले ही यह संरचना तत्वों की एक महाकाव्य संख्या रखती है, क्योंकि यह केवल छोटे सन्निहित ब्लॉकों का अनुरोध करता है जो ओएस के लिए एक चुनौती नहीं देते हैं ताकि बड़ी संख्या में सन्निहित अप्रयुक्त मिल जाए पृष्ठों की है।
- यह पूरी संरचना को लॉक किए बिना सुगमता और थ्रेड-सेफ्टी के लिए खुद को अच्छी तरह से उधार देता है, क्योंकि ऑपरेशन आमतौर पर अलग-अलग ब्लॉकों में स्थानीयकृत होते हैं।
अब मेरे लिए सबसे बड़ा पेशेवरों में से एक यह था कि इस डेटा संरचना का एक अपरिवर्तनीय संस्करण बनाना तुच्छ हो जाए, जैसे:
तब से, इसने सभी प्रकार के दरवाजों को खोल दिया और अधिक कार्य लिखने से साइड इफेक्ट्स से रहित हो गए, जिससे अपवाद-सुरक्षा, धागा-सुरक्षा, आदि को प्राप्त करना बहुत आसान हो गया। अपरिवर्तनीयता एक तरह की चीज थी जिसे मैंने खोजा था जिससे मैं आसानी से हासिल कर सकता था। इस डेटा संरचना में बाधा और दुर्घटना से, लेकिन यकीनन यह सबसे अच्छा लाभ में से एक है क्योंकि यह कोडबेस को बनाए रखना बहुत आसान है।
ग़ैर-क़ानूनी सरणियों में कैश लोकलिटी नहीं होती है जिसके परिणामस्वरूप खराब प्रदर्शन होता है। हालांकि 4M ब्लॉक आकार में ऐसा लगता है कि अच्छी कैशिंग के लिए पर्याप्त स्थान होगा।
उस आकार के ब्लॉक के साथ अपने आप को चिंतित करने के लिए संदर्भ का स्थान कुछ नहीं है, अकेले 4 किलोबाइट ब्लॉक दें। आमतौर पर एक कैश लाइन 64 बाइट्स की होती है। यदि आप कैश मिस को कम करना चाहते हैं, तो बस उन ब्लॉकों को ठीक से संरेखित करने पर ध्यान केंद्रित करें और जब संभव हो तो अधिक अनुक्रमिक एक्सेस पैटर्न का पक्ष लें।
एक यादृच्छिक-एक्सेस मेमोरी पैटर्न को अनुक्रमिक में बदलने का एक बहुत तेज़ तरीका एक बिटसेट का उपयोग करना है। मान लीजिए कि आपके पास सूचकांकों की एक नाव है और वे यादृच्छिक क्रम में हैं। आप बस उनके माध्यम से हल कर सकते हैं और बिटसेट में बिट्स को चिह्नित कर सकते हैं। फिर आप अपने बिटसेट के माध्यम से पुनरावृत्ति कर सकते हैं और जांच सकते हैं कि कौन से बाइट गैर-शून्य हैं, एक बार में 64-बिट्स की जांच, कहते हैं। एक बार जब आप 64-बिट्स के सेट का सामना करते हैं, जिसमें से कम से कम एक बिट सेट होता है, तो आप एफएफएस निर्देशों का उपयोग करके यह निर्धारित कर सकते हैं कि बिट्स सेट क्या हैं। बिट्स आपको बताते हैं कि आपको किन सूचकांकों तक पहुंचना चाहिए, सिवाय इसके कि आप अनुक्रमिक क्रम में छांटे गए सूचकांक प्राप्त करें।
यह कुछ ओवरहेड है, लेकिन कुछ मामलों में एक सार्थक आदान-प्रदान हो सकता है, खासकर यदि आप इन सूचकांकों पर कई बार लूपिंग करने जा रहे हैं।
किसी आइटम तक पहुंचना इतना सरल नहीं है, वहाँ एक अतिरिक्त स्तर अप्रत्यक्ष है। यह दूर अनुकूलित हो जाएगा? क्या इससे कैश की समस्या होगी?
नहीं, इसे दूर नहीं किया जा सकता है। रैंडम-एक्सेस, कम से कम, हमेशा इस संरचना के साथ अधिक खर्च होंगे। यह अक्सर आपके कैश में वृद्धि को याद नहीं करता है, हालांकि जब से आप ब्लॉक करने के लिए पॉइंटर्स की सरणी के साथ उच्च अस्थायी स्थानीयता प्राप्त करेंगे, खासकर यदि आपके सामान्य केस निष्पादन पथ अनुक्रमिक एक्सेस पैटर्न का उपयोग करते हैं।
चूँकि 4M सीमा के हिट होने के बाद रैखिक विकास होता है, इसलिए आपके पास सामान्य रूप से कई अधिक आवंटन हो सकते हैं (जैसे, 1GB मेमोरी के लिए अधिकतम 250 आवंटन)। 4M के बाद किसी अतिरिक्त मेमोरी की नकल नहीं की जाती है, हालांकि मुझे यकीन नहीं है कि अतिरिक्त आवंटन मेमोरी के बड़े हिस्से को कॉपी करने से अधिक महंगा है।
व्यवहार में नकल अक्सर तेज होती है क्योंकि यह एक दुर्लभ मामला है, केवल log(N)/log(2)
समय की तरह कुछ घटित होता है, साथ ही साथ गंदगी को सामान्य सामान्य मामला भी सरल बना देता है जहाँ आप सरणी को पूरा करने से पहले एक तत्व को कई बार लिख सकते हैं और उसे पुनः प्राप्त करने की आवश्यकता होती है। इसलिए आम तौर पर आपको इस प्रकार की संरचना के साथ तेजी से सम्मिलन नहीं मिलेगा क्योंकि सामान्य मामला काम अधिक महंगा है, भले ही इसके लिए विशाल सरणियों को फिर से बनाने के उस महंगे दुर्लभ मामले से निपटना न पड़े।
सभी विपक्ष के बावजूद मेरे लिए इस संरचना की प्राथमिक अपील में मेमोरी का उपयोग कम हो गया है, ओओएम के बारे में चिंता करने की ज़रूरत नहीं है, सूचकांकों और बिंदुओं को संग्रहीत करने में सक्षम होने के नाते जो अमान्य, संगामिति और अपरिवर्तनीयता प्राप्त नहीं करते हैं। एक डेटा संरचना होना अच्छा है जहां आप लगातार समय में चीजों को सम्मिलित और हटा सकते हैं जबकि यह आपके लिए खुद को साफ करता है और संरचना में इंगित और सूचक को अमान्य नहीं करता है।