C ++ में थ्रेड्स के बीच तेजी से संदेश पास करने के लिए मेमोरी मैनेजमेंट


9

मान लीजिए कि दो धागे हैं, जो एक-दूसरे को डेटा संदेश भेजते हुए अतुल्यकालिक संचार करते हैं। प्रत्येक थ्रेड में किसी प्रकार की संदेश कतार होती है।

मेरा प्रश्न बहुत निम्न स्तर का है: स्मृति के प्रबंधन के लिए सबसे कुशल तरीका क्या होने की उम्मीद की जा सकती है? मैं कई समाधानों के बारे में सोच सकता हूं:

  1. प्रेषक द्वारा वस्तु बनाता है new। रिसीवर कॉल delete
  2. मेमोरी पूलिंग (प्रेषक को मेमोरी वापस ट्रांसफर करने के लिए)
  3. कचरा संग्रह (जैसे, बोहम जीसी)
  4. (यदि वस्तुएं काफी छोटी हैं) पूरी तरह से ढेर आवंटन से बचने के लिए मूल्य से कॉपी करें

1) सबसे स्पष्ट समाधान है, इसलिए मैं इसे एक प्रोटोटाइप के लिए उपयोग करूँगा। संभावना है कि यह पहले से ही काफी अच्छा है। लेकिन मेरी विशिष्ट समस्या से स्वतंत्र, मुझे आश्चर्य है कि यदि आप प्रदर्शन के लिए अनुकूलन कर रहे हैं तो कौन सी तकनीक सबसे अधिक आशाजनक है।

मुझे उम्मीद है कि पूलिंग सैद्धांतिक रूप से सबसे अच्छी होगी, खासकर क्योंकि आप थ्रेड्स के बीच जानकारी के प्रवाह के बारे में अतिरिक्त ज्ञान का उपयोग कर सकते हैं। हालांकि, मुझे डर है कि सही होना भी सबसे मुश्किल है। ट्यूनिंग के बहुत सारे ... :(

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

यदि ऑब्जेक्ट छोटे और सरल हैं, तो मूल्य द्वारा प्रतिलिपि सबसे तेज़ हो सकती है। हालांकि, मुझे डर है कि यह समर्थित संदेशों के कार्यान्वयन पर अनावश्यक सीमाओं को बाध्य करता है, इसलिए मैं इससे बचना चाहता हूं।

जवाबों:


9

यदि ऑब्जेक्ट छोटे और सरल हैं, तो मूल्य द्वारा प्रतिलिपि सबसे तेज़ हो सकती है। हालांकि, मुझे डर है कि यह समर्थित संदेशों के कार्यान्वयन पर अनावश्यक सीमाओं को बाध्य करता है, इसलिए मैं इससे बचना चाहता हूं।

यदि आप एक ऊपरी सीमा का अनुमान लगा सकते हैं char buf[256], उदाहरण के लिए एक व्यावहारिक विकल्प यदि आप केवल दुर्लभ मामलों में हीप आवंटन को आमंत्रित नहीं कर सकते हैं:

struct Message
{
    // Stores the message data.
    char buf[256];

    // Points to 'buf' if it fits, heap otherwise.
    char* data;
};

3

यह निर्भर करता है कि आप कतारों को कैसे लागू करते हैं।

यदि आप एक सरणी (राउंड रॉबिन स्टाइल) के साथ जाते हैं, तो आपको समाधान के लिए आकार में एक ऊपरी बाउंड सेट करने की आवश्यकता है। 4. यदि आप एक लिंक किए गए कतार के साथ जाते हैं, तो आपको आवंटित ऑब्जेक्ट की आवश्यकता होती है।

फिर, संसाधन पूलिंग आसानी से की जा सकती है जब आप बस नए को बदलते हैं और साथ हटाते हैं AllocMessage<T>और freeMessage<T>। मेरा सुझाव यह होगा कि Tकंक्रीट आवंटित करते समय संभावित आकारों की मात्रा को सीमित और गोल किया जा सके messages

सीधे कचरा संग्रह काम कर सकता है लेकिन यह एक बड़े हिस्से को इकट्ठा करने की आवश्यकता होने पर लंबे समय तक रुक सकता है, और नए / हटाए जाने की तुलना में थोड़ा बुरा प्रदर्शन करेगा।


3

यदि C ++ में इसका उपयोग किया जाता है, तो बस एक स्मार्ट पॉइंटर्स का उपयोग करें - unique_ptr आपके लिए अच्छा काम करेगा, क्योंकि यह अंतर्निहित ऑब्जेक्ट को तब तक नहीं हटाएगा , जब तक कि किसी का उस पर कोई हैंडल न हो। आप पीटीआर ऑब्जेक्ट को वैल्यू द्वारा रिसीवर को पास करते हैं और कभी भी इस बारे में चिंता करने की ज़रूरत नहीं है कि कौन सा धागा इसे डिलीट करना चाहिए (उन मामलों में जहां रिसीवर को ऑब्जेक्ट प्राप्त नहीं होता है)।

आपको अभी भी थ्रेड्स के बीच लॉकिंग को हैंडल करना होगा, लेकिन प्रदर्शन अच्छा होगा क्योंकि कोई भी मेमोरी कॉपी नहीं होती (केवल ptr ऑब्जेक्ट ही होता है, जो कि छोटा है)।

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


2
लॉकिंग आमतौर पर मेमोरी कॉपी करने की तुलना में बहुत बड़ी समस्या है। बस केह रहा हू।
tdammers

जब आप लिखते हैं unique_ptr, मुझे लगता है कि आपका मतलब है shared_ptr। लेकिन इस बात में कोई संदेह नहीं है कि एक स्मार्ट पॉइंटर का उपयोग करना संसाधन प्रबंधन के लिए अच्छा है, यह इस तथ्य को नहीं बदलता है कि आप मेमोरी आवंटन और टैक्लोकास्ट के कुछ फॉर्म का उपयोग कर रहे हैं। मुझे लगता है कि यह सवाल अधिक निम्न स्तर का है।
5gon12eder

3

किसी वस्तु को एक धागे से दूसरे धागे में संचार करते समय सबसे बड़ा प्रदर्शन हिट होता है जो लॉक को हथियाने का ओवरहेड है। यह कई माइक्रोसेकंड के क्रम पर है, जो औसत समय new/ जोड़ी delete(सौ नैनोसेकंड के क्रम पर) की तुलना में काफी अधिक है । साने newकार्यान्वयन अपने प्रदर्शन से बचने के लिए लगभग हर कीमत पर लॉकिंग से बचने की कोशिश करते हैं।

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

  1. रिंग बफर का उपयोग करें। दोनों प्रक्रियाएं एक पॉइंटर को इस बफ़र में नियंत्रित करती हैं, एक रीड पॉइंटर है, दूसरा राइट पॉइंटर है।

    • प्रेषक पहले जांचता है कि बिंदुओं की तुलना करके एक तत्व जोड़ने के लिए जगह है, तो तत्व जोड़ता है, फिर लेखन सूचक को बढ़ाता है।

    • रिसीवर जांचता है कि अगर पॉइंटर्स की तुलना करके पढ़ने के लिए कोई तत्व है, तो तत्व को पढ़ता है, फिर रीड पॉइंटर को बढ़ाता है।

    बिंदुओं को एटोमिकल होने की आवश्यकता है क्योंकि वे थ्रेड्स के बीच साझा किए जाते हैं। हालांकि, प्रत्येक पॉइंटर को केवल एक थ्रेड द्वारा संशोधित किया जाता है, अन्य को केवल पॉइंटर तक पहुंच की आवश्यकता होती है। बफ़र के तत्व स्वयं संकेत हो सकते हैं, जो आपको अपने रिंग बफ़र को आसानी से आकार देने की अनुमति देता है जो प्रेषक ब्लॉक नहीं करेगा।

  2. लिंक की गई सूची का उपयोग करें जिसमें हमेशा कम से कम एक तत्व शामिल हो। रिसीवर के पास पहले तत्व के लिए एक संकेतक है, प्रेषक के पास अंतिम तत्व के लिए एक संकेतक है। ये पॉइंटर साझा नहीं किए गए हैं।

    • प्रेषक लिंक्ड सूची के लिए एक नया नोड बनाता है, इसके nextपॉइंटर को सेट करता है nullptr। फिर यह nextनए तत्व को इंगित करने के लिए अंतिम तत्व के पॉइंटर को अपडेट करता है । अंत में, यह अपने स्वयं के सूचक में नए तत्व को संग्रहीत करता है।

    • रिसीवर nextपहले तत्व के सूचक को देखता है कि क्या नया डेटा उपलब्ध है। यदि ऐसा है, तो यह पुराने पहले तत्व को हटा देता है, अपने स्वयं के पॉइंटर को वर्तमान तत्व को इंगित करता है और इसे संसाधित करना शुरू कर देता है।

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

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

इसी तरह, यदि आपके पास कई पाठक और / या लेखक हैं, तो आप बहुत अधिक बर्बाद हैं: आप लॉक-कम योजना के साथ आने की कोशिश कर सकते हैं, लेकिन यह सबसे अच्छा लागू करने के लिए मुश्किल होगा। इन स्थितियों को एक ताला के साथ संभालना बहुत आसान है। हालांकि, एक बार जब आप लॉक को पकड़ लेते हैं, तो आप new/ deleteप्रदर्शन के बारे में चिंता करना बंद कर सकते हैं ।


+1 मुझे इस रिंग बफर सॉल्यूशन को CAS लूप्स का उपयोग करते हुए समवर्ती कतारों के विकल्प के रूप में देखना होगा। यह बहुत ही आशाजनक लगता है।
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.