आधुनिक C ++ में पोर्ट / वैल्यू स्टोर डेवलपमेंट पोर्टिंग


9

मैं कैसेंड्रा के समान एक डेटाबेस सर्वर विकसित कर रहा हूं।

सी में विकास शुरू किया गया था, लेकिन चीजें कक्षाओं के बिना बहुत जटिल हो गईं।

वर्तमान में मैंने C ++ 11 में सब कुछ चित्रित किया है, लेकिन मैं अभी भी "आधुनिक" C ++ सीख रहा हूं और बहुत सारी चीजों के बारे में संदेह है।

डेटाबेस कुंजी / मूल्य जोड़े के साथ काम करेगा। प्रत्येक जोड़ी के पास कुछ और जानकारी होती है - जब भी बनाई जाती है जब वह समाप्त हो जाएगी (0 यदि समाप्त नहीं होती है)। प्रत्येक जोड़ी अपरिवर्तनीय है।

कुंजी सी स्ट्रिंग है, मान शून्य है *, लेकिन कम से कम इस समय मैं सी स्ट्रिंग के साथ ही मूल्य के साथ काम कर रहा हूं।

अमूर्त IListवर्ग हैं। यह तीन वर्गों से विरासत में मिला है

  • VectorList - सी गतिशील सरणी - एसटीडी के समान :: वेक्टर, लेकिन उपयोग करता है realloc
  • LinkList - जाँच और प्रदर्शन तुलना के लिए बनाया गया है
  • SkipList - अंत में उपयोग किया जाएगा कि वर्ग।

भविष्य में मैं Red Blackपेड़ भी लगा सकता हूं ।

प्रत्येक IListमें जोड़े से शून्य या अधिक पॉइंटर्स होते हैं, जो कुंजी द्वारा क्रमबद्ध होते हैं।

यदि IListबहुत लंबा हो गया, तो इसे डिस्क पर एक विशेष फ़ाइल में सहेजा जा सकता है। यह विशेष फ़ाइल एक तरह की है read only list

यदि आपको एक कुंजी की खोज करने की आवश्यकता है,

  • पहले स्मृति IListमें खोजा जाता है ( या SkipList, )।SkipListLinkList
  • फिर खोज तिथि द्वारा सॉर्ट की गई फ़ाइलों
    (नवीनतम फ़ाइल पहले, सबसे पुरानी फ़ाइल - अंतिम) पर भेजी जाती है ।
    ये सभी फाइलें मेमोरी में mmap-ed हैं।
  • अगर कुछ नहीं मिला, तो चाबी नहीं मिली।

मुझे IListचीजों के कार्यान्वयन के बारे में कोई संदेह नहीं है ।


वर्तमान में मुझे क्या उलझा रहा है:

जोड़े अलग-अलग आकार के होते हैं, वे द्वारा आवंटित किए जाते हैं new()और उन्होंने std::shared_ptrउन्हें इंगित किया है।

class Pair{
public:
    // several methods...
private:
    struct Blob;

    std::shared_ptr<const Blob> _blob;
};

struct Pair::Blob{
    uint64_t    created;
    uint32_t    expires;
    uint32_t    vallen;
    uint16_t    keylen;
    uint8_t     checksum;
    char        buffer[2];
};

"बफर" सदस्य चर विभिन्न आकार के साथ एक है। यह कुंजी + मान संग्रहीत करता है।
उदाहरण के लिए, यदि कुंजी 10 वर्ण की है, और मान 10 बाइट्स का है, तो पूरी वस्तु होगी sizeof(Pair::Blob) + 20(बफ़र का प्रारंभिक आकार 2 है, क्योंकि दो शून्य समाप्त बाइट्स हैं)

यह एक ही लेआउट डिस्क पर भी उपयोग किया जाता है, इसलिए मैं ऐसा कुछ कर सकता हूं:

// get the blob
Pair::Blob *blob = (Pair::Blob *) & mmaped_array[pos];

// create the pair, true makes std::shared_ptr not to delete the memory,
// since it does not own it.
Pair p = Pair(blob, true);

// however if I want the Pair to own the memory,
// I can copy it, but this is slower operation.
Pair p2 = Pair(blob);

हालाँकि यह भिन्न आकार C ++ कोड वाली बहुत सी जगहों पर एक समस्या है।

उदाहरण के लिए मैं उपयोग नहीं कर सकता std::make_shared()। यह मेरे लिए महत्वपूर्ण है, क्योंकि अगर मेरे पास 1M जोड़े हैं, तो मेरे पास 2M आवंटन होंगे।

दूसरी तरफ से, अगर मैं डायनेमिक ऐरे (उदाहरण के लिए [चार [123]) को "बफर" करता हूं, तो मैं एमएमएपी "ट्रिक" खो दूंगा, अगर मैं कुंजी जांचना चाहता हूं तो मेरे पास दो डीरेफरेंस होंगे और मैं सिंगल पॉइंटर जोड़ दूंगा - कक्षा को 8 बाइट्स।

मैंने सभी सदस्यों को अंदर से "खींचने" का प्रयास Pair::Blobकिया Pair, इसलिए Pair::Blobबस बफर होना चाहिए, लेकिन जब मैंने इसका परीक्षण किया, तो यह काफी धीमा था, शायद ऑब्जेक्ट डेटा को कॉपी करने के कारण।

एक और बदलाव जो मैं सोच रहा हूं वह यह है कि Pairकक्षा को हटा दें और इसे बदल दें और std::shared_ptrसभी तरीकों को "पुश" करें Pair::Blob, लेकिन यह मुझे चर आकार Pair::Blobवर्ग के साथ मदद नहीं करेगा ।

मैं सोच रहा हूं कि मैं कैसे वस्तु डिजाइन पर अधिक सी + + अनुकूल होने के लिए सुधार कर सकता हूं।


पूर्ण स्रोत कोड यहाँ है:
https://github.com/nmmmnu/HM3


2
आप उपयोग क्यों नहीं करते std::mapया std::unordered_map? मान (कुंजियों से जुड़े) कुछ क्यों हैं void*? आपको शायद किसी समय उन्हें नष्ट करने की आवश्यकता होगी; कैसे कब? आप टेम्प्लेट का उपयोग क्यों नहीं करते हैं?
बेसिल स्टारीनेवविच

मैं std :: map का उपयोग नहीं करता, क्योंकि मेरा मानना ​​है (या कम से कम कोशिश) std से बेहतर कुछ करने के लिए :: वर्तमान मामले के लिए नक्शा। लेकिन हां, मैं कुछ बिंदु पर सोच रहा हूं कि एसटीडी :: नक्शा लपेटें और इसे एक आईएलस्ट के रूप में भी प्रदर्शन की जांच करें।
निक

डील्लोकेशन और कॉलिंग d-tors किया जाता है जहां तत्व है IList::removeया जब IList नष्ट हो जाता है। इसमें बहुत समय लगता है, लेकिन मैं अलग धागे में करने जा रहा हूं। यह आसान होगा क्योंकि IList std::unique_ptr<IList>वैसे भी होगा । इसलिए मैं इसे नई सूची के साथ "स्विच" करने में सक्षम होऊंगा और पुरानी वस्तु को कहीं रख सकता हूं जहां मैं डी-टॉर कह सकता हूं।
निक

मैंने टेम्प्लेट की कोशिश की। वे यहां सबसे अच्छा समाधान नहीं हैं, क्योंकि यह उपयोगकर्ता पुस्तकालय नहीं है, कुंजी हमेशा है C stringऔर डेटा हमेशा कुछ बफर void *या है char *, इसलिए आप चार सरणी पास कर सकते हैं। आप समान में redisया पा सकते हैं memcached। कुछ बिंदु पर मैं std::stringकुंजी के लिए चार सरणी का उपयोग करने या तय करने का निर्णय ले सकता था , लेकिन यह अभी भी सी स्ट्रिंग होगा।
निक

6
4 टिप्पणियों को जोड़ने के बजाय, आपको अपने प्रश्न
बेसिल स्टायरनेविच

जवाबों:


3

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

फिर, मैं आपको सलाह दूंगा कि आप जितना संभव हो उतने ही नंगे, और जितना संभव हो उतना साफ-सुथरा क्रियान्वयन प्रदान करें। मेरे लिए ऐसा लगता है कि unordered_mapआपकी पहली पसंद होनी चाहिए, या mapहो सकता है कि इंटरफ़ेस द्वारा किसी प्रकार की चाबियों का आदेश दिया जाना चाहिए।

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

फिर, इसे प्रोफाइल करें, और देखें कि इंटरफ़ेस में बदलाव किए बिना, कार्यान्वयन में क्या सुधार करने की आवश्यकता है। या आपके पास प्रोफ़ाइल सुधारने के तरीके के बारे में आपके अपने विचार हो सकते हैं, इससे पहले कि आप प्रोफ़ाइल भी करें। यह ठीक है, लेकिन यह है अभी भी समय में किसी भी बिंदु पर पहले इन विचारों पर काम करने के लिए कोई कारण नहीं है।

आप कहते हैं कि आप इससे बेहतर करने की उम्मीद करते हैं map; इसके बारे में दो बातें कही जा सकती हैं:

ए) आप शायद नहीं करेंगे;

बी) हर कीमत पर समय से पहले अनुकूलन से बचें।

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

C ++ में मेमोरी प्रबंधन के बारे में जाने के विभिन्न तरीके हैं, और newऑपरेटर को ओवरलोड करने की क्षमता काम में आ सकती है। आपकी परियोजना के लिए एक सरलीकृत स्मृति आवंटन बाइट्स के एक विशाल सरणी का प्रचार करेगा और इसे ढेर के रूप में उपयोग करेगा। ( byte* heap।) आपके पास एक firstFreeByteसूचकांक होगा , जो शून्य से आरंभिक होगा, जो कि ढेर में पहले मुफ्त बाइट को इंगित करता है। जब Nबाइट्स के लिए एक अनुरोध आता है, तो आप पता वापस करते हैं heap + firstFreeByteऔर आप जोड़ते Nहैं firstFreeByte। इसलिए, मेमोरी आवंटन इतना तेज और कुशल हो जाता है कि यह वस्तुतः कोई समस्या नहीं रह जाती है।

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

चूंकि आपका डेटा अपरिवर्तनीय है, यह एक अच्छा समाधान है। यह आपको चर लंबाई की वस्तुओं के विचार को त्यागने की अनुमति देता है, और प्रत्येक के पास Pairइसके डेटा के लिए एक पॉइंटर होना चाहिए जैसा कि डेटा मेमोरी के लिए अतिरिक्त मेमोरी आवंटन लगभग कुछ भी नहीं है।

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

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

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