क्या अस्वास्थ्यकर सरणियाँ प्रदर्शनकारी हैं?


12

C # में, जब कोई उपयोगकर्ता एक बनाता है List<byte>और उसमें बाइट जोड़ता है, तो एक मौका है कि वह अंतरिक्ष से बाहर चला जाता है और अधिक स्थान आवंटित करने की आवश्यकता होती है। यह पिछले सरणी के आकार को दोगुना (या कुछ अन्य गुणक) आवंटित करता है, बाइट्स को कॉपी करता है और पुराने सरणी के संदर्भ को छोड़ देता है। मुझे पता है कि सूची तेजी से बढ़ती है क्योंकि प्रत्येक आवंटन महंगा है और यह O(log n)आवंटन को सीमित करता है , जहां 10हर बार अतिरिक्त आइटम जोड़ने से O(n)आवंटन में परिणाम होगा ।

हालांकि बड़े सरणी आकारों के लिए बहुत अधिक जगह बर्बाद हो सकती है, शायद लगभग आधा सरणी। मेमोरी को कम करने के लिए मैंने एक समान श्रेणी लिखी NonContiguousArrayListजो List<byte>एक बैकिंग स्टोर के रूप में उपयोग की जाती है अगर सूची में 4MB से कम थे, तो यह NonContiguousArrayListआकार में बढ़ने के साथ अतिरिक्त 4MB बाइट सरणियों को आवंटित करेगा ।

List<byte>इन सरणियों के विपरीत गैर-सन्निहित हैं, इसलिए चारों ओर डेटा की नकल नहीं है, बस एक अतिरिक्त 4 एम आवंटन। जब एक आइटम को देखा जाता है, तो आइटम को रखने वाले सरणी के सूचकांक को प्राप्त करने के लिए सूचकांक को 4M से विभाजित किया जाता है, फिर सरणी के भीतर सूचकांक प्राप्त करने के लिए modulo 4M।

क्या आप इस दृष्टिकोण के साथ समस्याओं को इंगित कर सकते हैं? यहाँ मेरी सूची है:

  • ग़ैर-क़ानूनी सरणियों में कैश लोकलिटी नहीं होती है जिसके परिणामस्वरूप खराब प्रदर्शन होता है। हालांकि 4M ब्लॉक आकार में ऐसा लगता है कि अच्छी कैशिंग के लिए पर्याप्त स्थान होगा।
  • किसी आइटम तक पहुंचना इतना सरल नहीं है, वहाँ एक अतिरिक्त स्तर अप्रत्यक्ष है। यह दूर अनुकूलित हो जाएगा? क्या इससे कैश की समस्या होगी?
  • चूँकि 4M सीमा के हिट होने के बाद रैखिक विकास होता है, इसलिए आपके पास सामान्य रूप से कई अधिक आवंटन हो सकते हैं (जैसे, 1GB मेमोरी के लिए अधिकतम 250 आवंटन)। 4M के बाद किसी अतिरिक्त मेमोरी की नकल नहीं की जाती है, हालांकि मुझे यकीन नहीं है कि अतिरिक्त आवंटन मेमोरी के बड़े हिस्से को कॉपी करने से अधिक महंगा है।

8
आपने थ्योरी को समाप्त कर दिया है (कैश को ध्यान में रखते हुए, एसिम्प्टोटिक जटिलता पर चर्चा की है), जो कुछ बचा है वह मापदंडों में प्लग करना है (यहां, 4M आइटम प्रति सबलिस्ट) और शायद माइक्रो-ऑप्टिमाइज़। अब बेंचमार्क करने का समय है, क्योंकि हार्डवेयर और कार्यान्वयन को ठीक किए बिना, आगे के प्रदर्शन पर चर्चा करने के लिए बहुत कम डेटा है।

3
यदि आप एकल संग्रह में 4 मिलियन से अधिक तत्वों के साथ काम कर रहे हैं, तो मुझे उम्मीद है कि कंटेनर माइक्रो-ऑप्टिमाइज़ेशन आपके प्रदर्शन की चिंताओं से कम है।
तेलस्तीन

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

@ डोवल यह वास्तव में एक अनियंत्रित लिंक्ड सूची नहीं है, क्योंकि 4 एम चंक्स खुद एक सरणी में संग्रहीत होते हैं, इसलिए किसी भी तत्व तक पहुंच O (1) नहीं O (n / B) है जहां B ब्लॉक आकार है।

2
@ user2313838 यदि 1000MB मेमोरी और 350MB सरणी है, तो सरणी को विकसित करने के लिए आवश्यक मेमोरी 1050MB होगी, जो उपलब्ध है उससे अधिक है, यह मुख्य समस्या है, आपकी प्रभावी सीमा 1 / 3rd आपकी कुल जगह है। TrimExcessकेवल तभी मदद करेगा जब सूची पहले से ही बनाई गई हो, और तब भी उसे कॉपी के लिए पर्याप्त स्थान की आवश्यकता हो।
noisecapella 16

जवाबों:


5

आपके द्वारा बताए गए पैमानों पर, चिंताएं उन सभी से अलग होती हैं जिनका आपने उल्लेख किया है।

कैश स्थानीयता

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

डेटा तत्व एक्सेस पैटर्न

  • यह लेख नेत्रहीन दिखाता है कि मेमोरी एक्सेस पैटर्न प्रदर्शन को कैसे प्रभावित करेगा।
  • संक्षेप में, बस यह ध्यान रखें कि यदि आपका एल्गोरिथ्म पहले से ही मेमोरी बैंडविड्थ द्वारा अड़चन में है, तो प्रदर्शन को बेहतर बनाने का एकमात्र तरीका डेटा के साथ अधिक उपयोगी कार्य करना है जो पहले से ही कैश में लोड है।
  • दूसरे शब्दों में, भले ही YourList[k]और YourList[k+1]लगातार होने की उच्च संभावना हो (एक नहीं होने के चार-मिलियन संभावना में), यह तथ्य प्रदर्शन में मदद नहीं करेगा यदि आप अपनी सूची को पूरी तरह से यादृच्छिक रूप से एक्सेस करते हैं, या बड़े अप्रत्याशित स्ट्रैंड में जैसे।while { index += random.Next(1024); DoStuff(YourList[index]); }

जीसी प्रणाली के साथ सहभागिता

  • मेरी राय में, यह वह जगह है जहाँ आपको सबसे अधिक ध्यान केंद्रित करना चाहिए।
  • कम से कम, यह समझें कि आपका डिज़ाइन किस तरह से बातचीत करेगा:
  • मैं इन विषयों का जानकार नहीं हूं इसलिए मैं दूसरों को योगदान देने के लिए छोड़ दूंगा।

पता ऑफसेट गणना का ओवरहेड

  • विशिष्ट C # कोड पहले से ही बहुत अधिक ऑफ़सेट ऑफ़सेट गणना कर रहा है, इसलिए आपकी योजना से अतिरिक्त ओवरहेड किसी एकल सरणी पर काम करने वाले विशिष्ट C # कोड से अधिक खराब नहीं होगा।
    • याद रखें कि C # कोड सरणी श्रेणी की जाँच भी करता है; और यह तथ्य C # को C ++ कोड के साथ तुलनीय सरणी प्रसंस्करण प्रदर्शन तक पहुंचने से नहीं रोकता है।
    • कारण यह है कि प्रदर्शन ज्यादातर मेमोरी बैंडविड्थ द्वारा अड़चन है।
    • मेमोरी बैंडविड्थ से उपयोगिता को अधिकतम करने की चाल मेमोरी रीड / राइट ऑपरेशन के लिए SIMD निर्देशों का उपयोग करना है। न तो विशिष्ट C # और न ही विशिष्ट C ++ ऐसा करता है; आपको पुस्तकालयों या भाषा के ऐड का सहारा लेना होगा।

यह बताने के लिए कि:

  • पता गणना करें
  • (ओपी के मामले में, chunk आधार पता लोड करें (जो पहले से कैश में है) और फिर अधिक पता गणना करें
  • तत्व पते से लिखें / लिखें

अंतिम चरण में अभी भी शेर का समय लगता है।

व्यक्तिगत सुझाव

  • आप एक CopyRangeफ़ंक्शन प्रदान कर सकते हैं , जो Array.Copyफ़ंक्शन की तरह व्यवहार करेगा लेकिन आपके दो उदाहरणों के NonContiguousByteArrayबीच या एक उदाहरण और एक अन्य सामान्य के बीच काम करेगा byte[]। ये फ़ंक्शन अधिकतम मेमोरी बैंडविड्थ उपयोग के लिए SIMD कोड (C ++ या C #) का उपयोग कर सकते हैं, और फिर आपका C # कोड कॉपी किए गए रेंज पर कई डीरेफरेंसिंग या एड्रेस गणना के ओवरहेड के बिना काम कर सकता है।

प्रयोज्यता और अंतर-संबंधी चिंताएँ

  • जाहिरा तौर पर आप NonContiguousByteArrayकिसी भी C #, C ++ या विदेशी भाषा के पुस्तकालयों के साथ इसका उपयोग नहीं कर सकते हैं , जो सन्निहित बाइट सरणियों या बाइट सरणियों की अपेक्षा करते हैं जिन्हें पिन किया जा सकता है।
  • हालाँकि, यदि आप अपना C ++ त्वरण पुस्तकालय (P / Invoke या C ++ / CLI के साथ) लिखते हैं, तो आप अंतर्निहित कोड में कई 4MB ब्लॉक के आधार पते की सूची में पास कर सकते हैं।
    • उदाहरण के लिए, यदि आप पर शुरू तत्वों को एक्सेस देनी (3 * 1024 * 1024)और पर समाप्त (5 * 1024 * 1024 - 1), इस साधन का उपयोग पर होते हैं जाएगा chunk[0]और chunk[1]। फिर आप बाइट सरणियों (आकार 4 एम) की एक सरणी (आकार 2) का निर्माण कर सकते हैं, इन ठिकानों को पिन कर सकते हैं और उन्हें अंतर्निहित कोड में पास कर सकते हैं।
  • एक और प्रयोज्य चिंता यह है कि आप IList<byte>इंटरफ़ेस को कुशलता से लागू करने में सक्षम नहीं होंगे : Insertऔर Removeप्रक्रिया में अभी बहुत समय लगेगा क्योंकि उन्हें O(N)समय की आवश्यकता होगी ।
    • वास्तव में, ऐसा लगता है कि आप इसके अलावा कुछ भी लागू नहीं कर सकते IEnumerable<byte>, अर्थात इसे क्रमिक रूप से स्कैन किया जा सकता है और यही है।

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

6

यह ध्यान देने योग्य है कि C ++ में पहले से ही मानक द्वारा एक समान संरचना है std::deque,। वर्तमान में, यह सामान के रैंडम-एक्सेस अनुक्रम की आवश्यकता के लिए डिफ़ॉल्ट विकल्प के रूप में अनुशंसित है।

वास्तविकता यह है कि डेटा के एक निश्चित आकार में जाने के बाद सन्निहित मेमोरी लगभग पूरी तरह से अनावश्यक हो जाती है- एक कैश लाइन सिर्फ 64 बाइट्स होती है और एक पेज का आकार सिर्फ 4-8KB (वर्तमान में विशिष्ट मान) होता है। एक बार जब आप कुछ एमबी के बारे में बात करना शुरू कर रहे हैं तो यह वास्तव में एक चिंता का विषय है। आवंटन लागत का भी यही हाल है। उस डेटा को संसाधित करने की कीमत- यहां तक ​​कि इसे पढ़ने से भी - वैसे भी आवंटन की कीमत बौनी हो जाती है।

इसके बारे में चिंता करने का एकमात्र कारण सी एपीआई के साथ हस्तक्षेप करने के लिए है। लेकिन आप किसी भी सूची के बफ़र को एक पॉइंटर वैसे भी प्राप्त नहीं कर सकते हैं इसलिए यहां कोई चिंता नहीं है।


यह दिलचस्प है, मुझे नहीं पता dequeथा कि एक समान कार्यान्वयन था
noisecapella

वर्तमान में कौन std :: deque की सिफारिश कर रहा है? क्या आप कोई स्रोत प्रदान कर सकते हैं? मैं हमेशा सोचा था कि सीडी :: वेक्टर अनुशंसित डिफ़ॉल्ट विकल्प था।
Teimpz

std::dequeवास्तव में आंशिक रूप से हतोत्साहित किया जाता है, क्योंकि एमएस मानक पुस्तकालय कार्यान्वयन बहुत बुरा है।
सेबेस्टियन रेडल

3

जब मेमोरी चंक को समय पर विभिन्न बिंदुओं पर आवंटित किया जाता है, जैसा कि आपके डेटा संरचना के भीतर उप-सरणियों में होता है, तो वे स्मृति में एक दूसरे से बहुत दूर स्थित हो सकते हैं। यह एक समस्या है या नहीं यह सीपीयू पर निर्भर करता है और अब किसी भी भविष्यवाणी करना बहुत कठिन है। आपको इसका परीक्षण करना होगा।

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

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

अतिरिक्त आबंटन केवल एक समस्या है अगर आपके उप-सरणी विखंडन छोटे हैं, क्योंकि प्रत्येक सरणी आवंटन में मेमोरी ओवरहेड है।

मैंने शब्दकोशों (हैश टेबल) के लिए समान संरचनाएँ बनाई हैं। .Net फ्रेमवर्क द्वारा प्रदान की जाने वाली डिक्शनरी में लिस्ट जैसी ही समस्या है। शब्दकोश में कठिन हैं कि आप के रूप में अच्छी तरह से rehashing से बचने की जरूरत है।


एक कॉम्पैक्ट कलेक्टर एक दूसरे के बगल में कॉम्पैक्ट हिस्सा ले सकता है।
डेडजैम

@DeadMG मैं उस स्थिति का उल्लेख कर रहा था जहां यह नहीं हो सकता है: बीच में अन्य विखंडू हैं, जो कचरा नहीं हैं। सूची <टी> के साथ, आप अपने सरणी के लिए सन्निहित स्मृति की गारंटी देते हैं। जब तक आपके पास सौभाग्यशाली कॉम्पैक्टिंग स्थिति नहीं होती है, तब तक एक चिन्हित सूची के साथ, स्मृति केवल एक चंक के भीतर सन्निहित है। लेकिन एक संघनन को भी बहुत सारे डेटा को स्थानांतरित करने की आवश्यकता हो सकती है, और बड़े सरणियाँ लार्ज ऑब्जेक्ट हीप में जाती हैं। यह जटिल है।
फ्रैंक हिलमैन

2

4M ब्लॉक आकार के साथ एक भी ब्लॉक भौतिक स्मृति में सन्निहित होने की गारंटी नहीं है; यह एक विशिष्ट VM पृष्ठ आकार से बड़ा है। उस पैमाने पर स्थानीयता सार्थक नहीं।

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


कॉम्पैक्टिंग जीसीएस विखंडन-मुक्त हैं।
डेडएमजी

यह सही है, लेकिन LOH संघनन .NET 4.5 के रूप में केवल तभी उपलब्ध है जब मैं सही ढंग से याद करता हूं।
user2313838

ढेर संघनन मानक के नकल-पर-पुनः प्राप्ति व्यवहार की तुलना में अधिक ओवरहेड को उकसा सकता है List
user2313838

एक बड़ी पर्याप्त और उचित आकार की वस्तु वैसे भी प्रभावी रूप से विखंडन-मुक्त है।
डेडजैम

2
@ डीडएमजीएम: जीसी कंपटीशन (इस 4 एमबी स्कीम के साथ) के बारे में सही चिंता यह है कि यह इन 4 एमबी बीफकेक्स के आसपास फावड़ा बेकार समय व्यतीत कर सकता है। परिणामस्वरूप यह बड़े GC पॉज़ के परिणामस्वरूप हो सकता है। इस कारण से, इस 4 एमबी योजना का उपयोग करते समय, महत्वपूर्ण जीसी आंकड़ों की निगरानी करना महत्वपूर्ण है कि यह क्या कर रहा है, और सुधारात्मक कार्रवाई करने के लिए।
रवांग

1

मैं आपके कोडबेस (ईसीएस इंजन) के कुछ सबसे केंद्रीय हिस्सों का वर्णन करता हूं जो आपके द्वारा वर्णित डेटा संरचना के प्रकार के आसपास हैं, हालांकि यह छोटे सन्निहित ब्लॉक (4 मेगाबाइट के बजाय 4 किलोबाइट्स की तरह) का उपयोग करता है।

यहाँ छवि विवरण दर्ज करें

यह उस ब्लॉक के सूचकांकों के लिए ब्लॉक के अंदर डालने के लिए तैयार होने वाले ब्लॉक (जो ब्लॉक पूर्ण नहीं हैं) के लिए एक मुक्त सूची के साथ निरंतर-समय सम्मिलन और निष्कासन को प्राप्त करने के लिए एक डबल फ्री सूची का उपयोग करता है। प्रविष्टि पर पुनः प्राप्त होने के लिए तैयार है।

मैं इस संरचना के पेशेवरों और विपक्षों को कवर करूँगा। चलो कुछ विपक्ष के साथ शुरू करते हैं क्योंकि उनमें से कई हैं:

विपक्ष

  1. इस संरचना में std::vector(शुद्ध रूप से सन्निहित संरचना) की तुलना में कुछ सौ मिलियन तत्वों को सम्मिलित करने में लगभग 4 गुना अधिक समय लगता है । और मैं माइक्रो-ऑप्टिमाइज़ेशन में बहुत सभ्य हूं लेकिन सामान्य रूप से ऐसा करने के लिए सिर्फ वैचारिक रूप से अधिक काम करना है क्योंकि पहले ब्लॉक मुक्त सूची के शीर्ष पर मुक्त ब्लॉक का निरीक्षण करना है, फिर ब्लॉक का उपयोग करें और ब्लॉक से एक मुक्त सूचकांक पॉप करें फ्री लिस्ट, एलिमेंट को फ्री पोजीशन पर लिखें, और फिर चेक करें कि क्या ब्लॉक फुल है और ब्लॉक फ्री लिस्ट से ब्लॉक को पॉप करें या नहीं। यह अभी भी एक निरंतर-समय ऑपरेशन है, लेकिन पीछे की ओर धकेलने की तुलना में बहुत बड़ा स्थिरांक है std::vector
  2. अनुक्रमण के लिए अतिरिक्त अंकगणित और अप्रत्यक्ष की अतिरिक्त परत को देखते हुए यादृच्छिक-अभिगम पैटर्न का उपयोग करने वाले तत्वों तक पहुंचने में लगभग दोगुना समय लगता है।
  3. अनुक्रमिक पहुँच कुशलता से एक इटोमर डिज़ाइन में मैप नहीं होती है क्योंकि इट्रीमीटर को हर बार बढ़ने के बाद अतिरिक्त ब्रांचिंग करनी होती है।
  4. इसमें कुछ मेमोरी ओवरहेड है, आमतौर पर प्रति तत्व लगभग 1 बिट। 1 बिट प्रति तत्व अधिक ध्वनि नहीं हो सकती है, लेकिन यदि आप एक मिलियन 16-बिट पूर्णांक को संग्रहीत करने के लिए इसका उपयोग कर रहे हैं, तो यह 6.25% एक पूरी तरह से कॉम्पैक्ट सरणी की तुलना में अधिक मेमोरी उपयोग है। हालाँकि, व्यवहार में यह कम मेमोरी का उपयोग करने के लिए जाता है std::vectorजब तक कि आप vectorउस अतिरिक्त क्षमता को समाप्त करने के लिए जमा नहीं कर रहे हैं जो इसे सुरक्षित रखता है। इसके अलावा मैं आमतौर पर इस तरह के नन्हा तत्वों को संग्रहीत करने के लिए इसका इस्तेमाल नहीं करता।

पेशेवरों

  1. एक for_eachफ़ंक्शन का उपयोग करके अनुक्रमिक पहुंच जो एक ब्लॉक के भीतर तत्वों की कॉलबैक प्रोसेसिंग रेंज लेता है, लगभग std::vector10% (जैसे केवल 10% अंतर) के साथ अनुक्रमिक पहुंच की गति को प्रतिद्वंद्वी करता है , इसलिए यह मेरे लिए सबसे अधिक प्रदर्शन-महत्वपूर्ण उपयोग मामलों में बहुत कम कुशल नहीं है ( एक ईसीएस इंजन में बिताया जाने वाला अधिकांश समय अनुक्रमिक पहुंच में है)।
  2. जब वे पूरी तरह से खाली हो जाते हैं, तो संरचना से निपटने वाले ब्लॉकों के बीच से निरंतर-समय के निष्कासन की अनुमति देता है। परिणामस्वरूप यह आम तौर पर यह सुनिश्चित करने में काफी सभ्य है कि डेटा संरचना कभी भी आवश्यकता से अधिक मेमोरी का उपयोग नहीं करती है।
  3. यह उन तत्वों के सूचकांकों को अमान्य नहीं करता है, जो कंटेनर से सीधे नहीं हटाए जाते हैं क्योंकि यह बाद में सम्मिलन पर उन छेदों को पुनः प्राप्त करने के लिए एक मुफ्त सूची दृष्टिकोण का उपयोग करके पीछे छोड़ देता है।
  4. आपको स्मृति से बाहर चलने के बारे में बहुत चिंता करने की ज़रूरत नहीं है, भले ही यह संरचना तत्वों की एक महाकाव्य संख्या रखती है, क्योंकि यह केवल छोटे सन्निहित ब्लॉकों का अनुरोध करता है जो ओएस के लिए एक चुनौती नहीं देते हैं ताकि बड़ी संख्या में सन्निहित अप्रयुक्त मिल जाए पृष्ठों की है।
  5. यह पूरी संरचना को लॉक किए बिना सुगमता और थ्रेड-सेफ्टी के लिए खुद को अच्छी तरह से उधार देता है, क्योंकि ऑपरेशन आमतौर पर अलग-अलग ब्लॉकों में स्थानीयकृत होते हैं।

अब मेरे लिए सबसे बड़ा पेशेवरों में से एक यह था कि इस डेटा संरचना का एक अपरिवर्तनीय संस्करण बनाना तुच्छ हो जाए, जैसे:

यहाँ छवि विवरण दर्ज करें

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

ग़ैर-क़ानूनी सरणियों में कैश लोकलिटी नहीं होती है जिसके परिणामस्वरूप खराब प्रदर्शन होता है। हालांकि 4M ब्लॉक आकार में ऐसा लगता है कि अच्छी कैशिंग के लिए पर्याप्त स्थान होगा।

उस आकार के ब्लॉक के साथ अपने आप को चिंतित करने के लिए संदर्भ का स्थान कुछ नहीं है, अकेले 4 किलोबाइट ब्लॉक दें। आमतौर पर एक कैश लाइन 64 बाइट्स की होती है। यदि आप कैश मिस को कम करना चाहते हैं, तो बस उन ब्लॉकों को ठीक से संरेखित करने पर ध्यान केंद्रित करें और जब संभव हो तो अधिक अनुक्रमिक एक्सेस पैटर्न का पक्ष लें।

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

यह कुछ ओवरहेड है, लेकिन कुछ मामलों में एक सार्थक आदान-प्रदान हो सकता है, खासकर यदि आप इन सूचकांकों पर कई बार लूपिंग करने जा रहे हैं।

किसी आइटम तक पहुंचना इतना सरल नहीं है, वहाँ एक अतिरिक्त स्तर अप्रत्यक्ष है। यह दूर अनुकूलित हो जाएगा? क्या इससे कैश की समस्या होगी?

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

चूँकि 4M सीमा के हिट होने के बाद रैखिक विकास होता है, इसलिए आपके पास सामान्य रूप से कई अधिक आवंटन हो सकते हैं (जैसे, 1GB मेमोरी के लिए अधिकतम 250 आवंटन)। 4M के बाद किसी अतिरिक्त मेमोरी की नकल नहीं की जाती है, हालांकि मुझे यकीन नहीं है कि अतिरिक्त आवंटन मेमोरी के बड़े हिस्से को कॉपी करने से अधिक महंगा है।

व्यवहार में नकल अक्सर तेज होती है क्योंकि यह एक दुर्लभ मामला है, केवल log(N)/log(2)समय की तरह कुछ घटित होता है, साथ ही साथ गंदगी को सामान्य सामान्य मामला भी सरल बना देता है जहाँ आप सरणी को पूरा करने से पहले एक तत्व को कई बार लिख सकते हैं और उसे पुनः प्राप्त करने की आवश्यकता होती है। इसलिए आम तौर पर आपको इस प्रकार की संरचना के साथ तेजी से सम्मिलन नहीं मिलेगा क्योंकि सामान्य मामला काम अधिक महंगा है, भले ही इसके लिए विशाल सरणियों को फिर से बनाने के उस महंगे दुर्लभ मामले से निपटना न पड़े।

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

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.