काइलोटन के सुझाव को प्रतिध्वनित करने के लिए, लेकिन जब संभव हो तो मैं डेटा संरचना स्तर पर इसे हल करने की सलाह दूंगा, न कि कम आवंटनकर्ता स्तर पर यदि आप इसकी मदद कर सकते हैं।
यहां एक सरल उदाहरण दिया गया है कि आप Foos
एक साथ जुड़े हुए तत्वों के साथ छेद के साथ एक सरणी का उपयोग करके बार-बार आवंटन और मुक्त करने से कैसे बच सकते हैं (इसे "आवंटनकर्ता" स्तर के बजाय "कंटेनर" स्तर पर हल कर सकते हैं):
struct FooNode
{
explicit FooNode(const Foo& ielement): element(ielement), next(-1) {}
// Stores a 'Foo'.
Foo element;
// Points to the next foo available; either the
// next used foo or the next deleted foo. Can
// use SoA and hoist this out if Foo doesn't
// have 32-bit alignment.
int next;
};
struct Foos
{
// Stores all the Foo nodes.
vector<FooNode> nodes;
// Points to the first used node.
int first_node;
// Points to the first free node.
int free_node;
Foos(): first_node(-1), free_node(-1)
{
}
const FooNode& operator[](int n) const
{
return data[n];
}
void insert(const Foo& element)
{
int index = free_node;
if (index != -1)
{
// If there's a free node available,
// pop it from the free list, overwrite it,
// and push it to the used list.
free_node = data[index].next;
data[index].next = first_node;
data[index].element = element;
first_node = index;
}
else
{
// If there's no free node available, add a
// new node and push it to the used list.
FooNode new_node(element);
new_node.next = first_node;
first_node = data.size() - 1;
data.push_back(new_node);
}
}
void erase(int n)
{
// If the node being removed is the first used
// node, pop it from the used list.
if (first_node == n)
first_node = data[n].next;
// Push the node to the free list.
data[n].next = free_node;
free_node = n;
}
};
इस आशय के लिए कुछ: एक स्वतंत्र सूची के साथ एक एकल-लिंक्ड सूचकांक सूची। अनुक्रमणिका लिंक आपको हटाए गए तत्वों को छोड़ने, स्थिर-समय में तत्वों को निकालने, और निरंतर-समय सम्मिलन के साथ मुक्त तत्वों को पुनः प्राप्त / पुनः उपयोग / अधिलेखित करने की अनुमति देते हैं। संरचना के माध्यम से पुनरावृत्ति करने के लिए, आप कुछ इस तरह करते हैं:
for (int index = foos.first_node; index != -1; index = foos[index].next)
// do something with foos[index]
और आप कॉपी असाइनमेंट की आवश्यकता से बचने के लिए टेम्प्लेट, प्लेसमेंट न्यू और मैनुअल डोरेट इनवोकेशन का उपयोग करके उपरोक्त प्रकार के "लिंक किए गए ऐरे ऑफ होल" डेटा संरचना को सामान्य कर सकते हैं, तत्वों को हटाए जाने पर इसे विध्वंसक बना देते हैं, एक फॉरवर्ड इटरेटर प्रदान करते हैं, आदि। बहुत स्पष्ट रूप से अवधारणा को स्पष्ट करने के लिए उदाहरण को बहुत सी-लाइक रखने के लिए चुना और इसलिए भी कि मैं बहुत आलसी हूं।
यह कहा कि, यह संरचना स्थानिक इलाकों में आपके द्वारा हटाए जाने और बीच से बहुत कुछ / चीजें सम्मिलित करने के बाद कम हो जाती है। उस बिंदु next
पर आप वेक्टर के साथ आगे और पीछे चल सकते हैं, डेटा को उसी अनुक्रमिक ट्रैवर्सल के भीतर कैश लाइन से पूर्व में हटाए गए डेटा को फिर से लोड करना (यह किसी भी डेटा संरचना या आवंटन के साथ अपरिहार्य है जो पुनः प्राप्त करते समय तत्वों को हटाने के बिना निरंतर समय निकालने की अनुमति देता है। निरंतर-समय सम्मिलन के साथ बीच से रिक्त स्थान और समानांतर बिटसेट या removed
ध्वज की तरह कुछ का उपयोग किए बिना । कैश-फ्रेंडली को पुनर्स्थापित करने के लिए, आप एक कॉपी ctor और स्वैप विधि इस तरह से लागू कर सकते हैं:
Foos(const Foos& other)
{
for (int index = other.first_node; index != -1; index = other[index].next)
insert(foos[index].element);
}
void Foos::swap(Foos& other)
{
nodes.swap(other.nodes):
std::swap(first_node, other.first_node);
std::swap(free_node, other.free_node);
}
// ... then just copy and swap:
Foos(foos).swap(foos);
अब नया संस्करण कैश-फ्रेंडली है फिर से ट्रैवर्स के लिए। एक अन्य विधि संरचना में सूचकांक की एक अलग सूची संग्रहीत करती है और उन्हें समय-समय पर सॉर्ट करती है। एक और बिटसेट का उपयोग यह इंगित करने के लिए है कि सूचकांकों का उपयोग क्या किया जाता है। यह हमेशा आपको अनुक्रमिक क्रम में बिटसेट का पता लगाने के लिए होगा (कुशलता से ऐसा करने के लिए, एफएफएस / एफएफजेड का उपयोग करके एक समय में 64-बिट की जांच करें)। बिटसेट सबसे कुशल और गैर-घुसपैठ है, जिसके लिए केवल एक समानांतर बिट प्रति तत्व की आवश्यकता होती है जो इंगित करता है कि कौन से उपयोग किए जाते हैं और जिन्हें 32-बिट next
सूचकांकों की आवश्यकता के बजाय हटा दिया जाता है , लेकिन सबसे अच्छा लिखने के लिए सबसे अधिक समय लगता है (यह नहीं होगा) ट्रैवर्सल के लिए तेज़ रहें यदि आप एक समय में एक बिट की जाँच कर रहे हैं - आपको कब्जे वाले सूचकांकों की सीमाओं का तेजी से निर्धारण करने के लिए एक बार में 32+ बिट्स के बीच एक सेट या परेशान बिट को खोजने के लिए FFS / FFZ की आवश्यकता है)।
यह जुड़ा हुआ समाधान आम तौर पर लागू करने और गैर-दखल देने के लिए सबसे आसान है ( Foo
कुछ removed
ध्वज को संग्रहीत करने के लिए संशोधित करने की आवश्यकता नहीं है ) यदि आप इस कंटेनर को किसी भी डेटा प्रकार के साथ काम करने के लिए सामान्य करना चाहते हैं तो यदि आप उस 32-बिट को बुरा नहीं मानते हैं तो यह उपयोगी है। प्रति तत्व ओवरहेड।
क्या मुझे गतिशील आवंटन के लिए कोई मेमोरी पूल बनाना चाहिए, या क्या इससे परेशान होने की कोई जरूरत नहीं है? क्या होगा अगर लक्ष्य मंच मोबाइल डिवाइस हैं?
आवश्यकता एक मजबूत शब्द है और मैं बहुत प्रदर्शन-महत्वपूर्ण क्षेत्रों में काम कर रहा हूँ जैसे कि किरण, छवि प्रसंस्करण, कण सिमुलेशन और जाल प्रसंस्करण, लेकिन यह बहुत ही महंगा है और गोलियों की तरह बहुत हल्की प्रसंस्करण के लिए इस्तेमाल की जाने वाली नन्हा वस्तुओं को आवंटित करना बहुत महंगा है। और एक सामान्य प्रयोजन के लिए व्यक्तिगत रूप से कण, चर-आकार मेमोरी आवंटनकर्ता। यह देखते हुए कि आपको अपनी इच्छित किसी भी चीज़ को संग्रहीत करने के लिए एक या दो दिन में उपरोक्त डेटा संरचना को सामान्य करने में सक्षम होना चाहिए, मुझे लगता है कि इस तरह के ढेर आवंटन / सौदेबाजी की लागत को खत्म करने के लिए हर एक नन्हा किशोर चीज़ के लिए भुगतान किया जाना एक सार्थक विनिमय होगा। आवंटन / सौदे की लागत को कम करने के शीर्ष पर, आपको परिणामों का पता लगाने (कम कैश मिस और पेज दोष, यानी) का पता लगाने के संदर्भ का बेहतर इलाका मिलता है।
जैसा कि जोश ने GC के बारे में उल्लेख किया है, मैंने C # के GC कार्यान्वयन को जावा के रूप में काफी करीब से अध्ययन नहीं किया है, लेकिन GC के आवंटनकर्ताओं के पास अक्सर एक है प्रारंभिक आवंटन करते हैंयह बहुत तेज़ है क्योंकि यह एक अनुक्रमिक आबंटक का उपयोग कर रहा है जो बीच से मेमोरी को मुक्त नहीं कर सकता है (लगभग एक स्टैक की तरह, आप चीजों को बीच में नहीं हटा सकते हैं)। फिर यह महंगी लागतों के लिए भुगतान करता है वास्तव में एक अलग थ्रेड में अलग-अलग ऑब्जेक्ट को मेमोरी को हटाने और पूर्व में आवंटित मेमोरी को पूरी तरह से शुद्ध करने की अनुमति देता है (जैसे एक लिंक संरचना की तरह कुछ और करने के लिए डेटा को कॉपी करते समय पूरे स्टैक को नष्ट करना), लेकिन क्योंकि यह एक अलग थ्रेड में किया गया है, इसलिए जरूरी नहीं कि यह आपके एप्लिकेशन के थ्रेड्स को इतना स्टाल करे। हालाँकि, यह एक अतिरिक्त स्तर के अप्रत्यक्ष स्तर की एक बहुत महत्वपूर्ण छिपी हुई लागत और प्रारंभिक जीसी चक्र के बाद एलओआर के सामान्य नुकसान को वहन करता है। हालांकि आवंटन में तेजी लाने के लिए यह एक और रणनीति है - इसे कॉलिंग थ्रेड में सस्ता बनाएं और फिर दूसरे में महंगा काम करें। इसके लिए आपको अपनी वस्तुओं को संदर्भित करने के लिए अप्रत्यक्ष के दो स्तरों की आवश्यकता है क्योंकि वे शुरू में आपके द्वारा पहले चक्र के बाद आवंटित किए गए समय के बीच स्मृति में फेरबदल करेंगे।
इसी तरह की नस में एक और रणनीति जो सी ++ में लागू करने के लिए थोड़ी आसान है, बस अपने मुख्य थ्रेड्स में अपनी वस्तुओं को मुक्त करने के लिए परेशान न करें। बस एक डेटा संरचना के अंत में जोड़ना और जोड़ना और जोड़ना जो बीच से चीजों को हटाने की अनुमति नहीं देता है। हालाँकि, उन चीजों को चिह्नित करें जिन्हें हटाने की आवश्यकता है। फिर एक अलग धागा हटाए गए तत्वों के बिना एक नई डेटा संरचना बनाने के महंगे काम का ख्याल रख सकता है और फिर पुराने एक के साथ नए को स्वैप कर सकता है, जैसे आवंटित करने और मुक्त करने वाले तत्वों की अधिकांश लागत एक पर पारित की जा सकती है। यदि आप यह धारणा बना सकते हैं कि किसी तत्व को हटाने का अनुरोध करने पर अलग थ्रेड को तत्काल संतुष्ट नहीं होना है। जहां तक आपके धागों का संबंध है, न केवल मुक्त करना सस्ता बनाता है, बल्कि आवंटन को सस्ता बनाता है, चूंकि आप एक बहुत ही सरल और डम्बर डेटा संरचना का उपयोग कर सकते हैं, जिसे कभी भी बीच से हटाने के मामलों को संभालना नहीं पड़ता है। यह एक कंटेनर की तरह है जिसे केवल एक की जरूरत हैpush_back
सम्मिलन के लिए फ़ंक्शन, clear
सभी तत्वों को हटाने के लिए एक फ़ंक्शन, और swap
हटाए गए तत्वों को छोड़कर एक नए, कॉम्पैक्ट कंटेनर के साथ सामग्री को स्वैप करने के लिए; यही वह है जहां तक उत्परिवर्तन होता है।