बेसिल के सुझाव के अनुसार, मैं बहुत सी चीजों पर जोर देता हूं, जहां एक स्ट्रिंग लुकअप स्टोर करने और तुलना करने के लिए 32-बिट इंडेक्स में अनुवाद करता है। मेरे मामले में यह उपयोगी है क्योंकि मेरे पास कभी-कभी "x" नाम की संपत्ति के साथ सैकड़ों-लाखों घटक होते हैं, उदाहरण के लिए, जिसे अभी भी उपयोगकर्ता के अनुकूल स्ट्रिंग नाम की आवश्यकता है क्योंकि इसे अक्सर नाम से स्क्रिप्टर्स द्वारा एक्सेस किया जाता है।
मैं लुकअप के लिए एक ट्राइ का उपयोग करता हूं (प्रयोग किया जाता है, unordered_map
लेकिन मेमोरी ट्यून द्वारा समर्थित मेरा ट्यून किया गया ट्राइब कम से कम बेहतर प्रदर्शन करना शुरू कर देता है और हर बार स्ट्रक्चर को एक्सेस किए बिना केवल थ्रेड-सेफ बनाना आसान होता है) लेकिन ऐसा नहीं है निर्माण के लिए उपवास के रूप में std::string
। यह बिंदु स्ट्रिंग समानता के लिए जाँच जैसे बाद के संचालन को गति देने के लिए अधिक है, जो मेरे मामले में, समानता के लिए दो पूर्णांकों की जाँच करने और स्मृति उपयोग को कम करने के लिए बस उबलता है।
मुझे लगता है कि एक विकल्प यह होगा कि पहले से आवंटित मूल्यों की किसी प्रकार की रजिस्ट्री को बनाए रखा जाए, लेकिन क्या यह संभव है कि रजिस्ट्री को अनावश्यक स्मृति आवंटन से अधिक तेज़ बनाया जाए?
यह एक एकल की तुलना में बहुत तेजी से डेटा संरचना के माध्यम से एक खोज करने के लिए कठिन होने जा रहा है malloc
, उदाहरण के लिए, यदि आपके पास एक मामला है जहां आप एक फ़ाइल की तरह बाहरी इनपुट से स्ट्रिंग्स का एक नाव लोड पढ़ रहे हैं, उदाहरण के लिए, तो मेरा प्रलोभन एक अनुक्रमिक आवंटन का उपयोग करना होगा यदि संभव हो तो। यह नकारात्मक पक्ष के साथ आता है कि आप एक व्यक्तिगत स्ट्रिंग की स्मृति को मुक्त नहीं कर सकते हैं। आवंटनकर्ता द्वारा जमा की गई सभी मेमोरी को एक बार में मुक्त किया जाना चाहिए या नहीं। लेकिन एक अनुक्रमिक आवंटनकर्ता उन मामलों में काम कर सकता है जहां आपको बस एक सीधे अनुक्रमिक फैशन में स्मृति के छोटे चर-आकार के टुकड़े को आवंटित करने की आवश्यकता होती है, केवल बाद में इसे दूर फेंकने के लिए। मुझे नहीं पता कि यह आपके मामले में लागू होता है या नहीं, लेकिन जब लागू होता है, तो यह लगातार किशोरी स्मृति आवंटन से संबंधित हॉटस्पॉट को ठीक करने का एक आसान तरीका हो सकता है (जो कि कैश मिस और पेज दोष के साथ अंतर्निहित से अधिक हो सकता है) द्वारा इस्तेमाल किया एल्गोरिथ्म, कहते हैं, malloc
)।
फिक्स्ड-आकार के आवंटन को अनुक्रमिक आवंटन अवरोधों के बिना तेजी से बढ़ाना आसान है जो आपको बाद में पुन: उपयोग किए जाने वाले मेमोरी के विशिष्ट विखंडों को मुक्त करने से रोकते हैं। लेकिन डिफ़ॉल्ट आवंटनकर्ता की तुलना में चर-आकार का आवंटन अधिक कठिन है। मूल रूप से किसी भी प्रकार का मेमोरी एलोकेटर बनाना जो कि तेजी से malloc
होता है, आम तौर पर बेहद कठिन होता है यदि आप बाधाओं को लागू नहीं करते हैं जो इसकी प्रयोज्यता को कम करते हैं। एक समाधान के लिए एक निश्चित आकार के आवंटनकर्ता का उपयोग करना है, कहते हैं, सभी तार जो 8 बाइट्स या उससे कम हैं यदि आपके पास उनमें से एक नाव लोड है और लंबे तार एक दुर्लभ मामला है (जिसके लिए आप बस डिफ़ॉल्ट आवंटनकर्ता का उपयोग कर सकते हैं)। इसका मतलब है कि 1-बाइट स्ट्रिंग्स के लिए 7 बाइट बर्बाद हो जाते हैं, लेकिन यह आवंटन से संबंधित हॉटस्पॉट्स को समाप्त कर देना चाहिए, अगर, कहते हैं, 95% समय, आपके स्ट्रिंग्स बहुत कम हैं।
एक और समाधान जो अभी मेरे पास हुआ है, वह है अनियंत्रित लिंक्ड सूची का उपयोग करना, जो पागल लग सकता है लेकिन मुझे सुन सकता है।
यहां विचार प्रत्येक अनियंत्रित नोड को चर-आकार के बजाय एक निश्चित आकार का बनाना है। जब आप ऐसा करते हैं, तो आप एक अत्यंत तेज़ फ़िक्स्ड-आकार वाले चंक एलोकेटर का उपयोग कर सकते हैं, जो मेमोरी को पूल करता है, जो एक साथ जुड़े चर-आकार के स्ट्रिंग्स के लिए निश्चित-आकार के चांस को आवंटित करता है। यह मेमोरी उपयोग को कम नहीं करेगा, यह लिंक की लागत के कारण इसे जोड़ देगा, लेकिन आप अपनी आवश्यकताओं के लिए उपयुक्त संतुलन खोजने के लिए अनियंत्रित आकार के साथ खेल सकते हैं। यह एक निराला विचार है, लेकिन स्मृति से संबंधित आकर्षण के केंद्र को खत्म करना चाहिए क्योंकि अब आप प्रभावी रूप से पूल स्मृति को पहले से ही भारी सन्निहित ब्लॉकों में आवंटित कर सकते हैं और अभी भी व्यक्तिगत रूप से तार मुक्त करने के लाभ हैं। यहाँ एक साधारण ol 'फिक्स्ड एलोकेटर है, जो मैंने लिखा है (चित्रण जिसे मैंने किसी और के लिए बनाया है, उत्पादन-संबंधित फ्लफ़ से रहित) जो आप स्वतंत्र रूप से उपयोग कर सकते हैं:
#ifndef FIXED_ALLOCATOR_HPP
#define FIXED_ALLOCATOR_HPP
class FixedAllocator
{
public:
/// Creates a fixed allocator with the specified type and block size.
explicit FixedAllocator(int type_size, int block_size = 2048);
/// Destroys the allocator.
~FixedAllocator();
/// @return A pointer to a newly allocated chunk.
void* allocate();
/// Frees the specified chunk.
void deallocate(void* mem);
private:
struct Block;
struct FreeElement;
FreeElement* free_element;
Block* head;
int type_size;
int num_block_elements;
};
#endif
#include "FixedAllocator.hpp"
#include <cstdlib>
struct FixedAllocator::FreeElement
{
FreeElement* next_element;
};
struct FixedAllocator::Block
{
Block* next;
char* mem;
};
FixedAllocator::FixedAllocator(int type_size, int block_size): free_element(0), head(0)
{
type_size = type_size > sizeof(FreeElement) ? type_size: sizeof(FreeElement);
num_block_elements = block_size / type_size;
if (num_block_elements == 0)
num_block_elements = 1;
}
FixedAllocator::~FixedAllocator()
{
// Free each block in the list, popping a block until the stack is empty.
while (head)
{
Block* block = head;
head = head->next;
free(block->mem);
free(block);
}
free_element = 0;
}
void* FixedAllocator::allocate()
{
// Common case: just pop free element and return.
if (free_element)
{
void* mem = free_element;
free_element = free_element->next_element;
return mem;
}
// Rare case when we're out of free elements.
// Create new block.
Block* new_block = static_cast<Block*>(malloc(sizeof(Block)));
new_block->mem = malloc(type_size * num_block_elements);
new_block->next = head;
head = new_block;
// Push all but one of the new block's elements to the free stack.
char* mem = new_block->mem;
for (int j=1; j < num_block_elements; ++j)
{
void* ptr = mem + j*type_size;
FreeElement* element = static_cast<FreeElement*>(ptr);
element->next_element = free_element;
free_element = element;
}
return mem;
}
void FixedAllocator::deallocate(void* mem)
{
// Just push a free element to the stack.
FreeElement* element = static_cast<FreeElement*>(mem);
element->next_element = free_element;
free_element = element;
}