मैं std क्यों करूँगा :: एक std ले जाएँ :: साझा किया गया?


148

मैं क्लैंग स्रोत कोड के माध्यम से देख रहा हूं और मुझे यह स्निपेट मिला:

void CompilerInstance::setInvocation(
    std::shared_ptr<CompilerInvocation> Value) {
  Invocation = std::move(Value);
}

मैं क्यों std::moveएक करना चाहते हैं std::shared_ptr?

क्या साझा संसाधन पर स्वामित्व स्थानांतरित करने का कोई बिंदु है?

मैं इसके बजाय ऐसा क्यों नहीं करूंगा?

void CompilerInstance::setInvocation(
    std::shared_ptr<CompilerInvocation> Value) {
  Invocation = Value;
}

जवाबों:


137

मुझे लगता है कि एक बात का दूसरे उत्तरों ने जोर नहीं दिया, वह है गति की बात ।

std::shared_ptrसंदर्भ गणना परमाणु है । संदर्भ संख्या को बढ़ाने या घटाने के लिए परमाणु वृद्धि या वृद्धि की आवश्यकता होती है । यह गैर-परमाणु वेतन वृद्धि / गिरावट की तुलना में सौ गुना धीमा है , यह उल्लेख करने के लिए नहीं कि यदि हम एक ही काउंटर को बढ़ाते और घटाते हैं तो हम प्रक्रिया में एक टन समय और संसाधनों को बर्बाद करते हैं।

ले जाकर shared_ptrबजाय इसे कॉपी करने के लिए, हम "चोरी" परमाणु संदर्भ गिनती और हम अन्य को रद्द shared_ptr। संदर्भ संख्या "चोरी" परमाणु नहीं है , और यह कॉपी करने shared_ptr(और परमाणु संदर्भ वृद्धि या क्षरण) की तुलना में सौ गुना तेज है ।

ध्यान दें कि इस तकनीक का उपयोग पूरी तरह से अनुकूलन के लिए किया जाता है। इसे कॉपी करना (जैसा आपने सुझाव दिया) ठीक कार्यक्षमता के अनुसार है।


5
क्या यह वास्तव में सौ गुना तेज है? क्या आपके पास इसके लिए बेंचमार्क हैं?
xaviersjs

1
@xaviersjs असाइनमेंट के लिए एक परमाणु वेतन वृद्धि की आवश्यकता होती है, जब मूल्य दायरे से बाहर हो जाता है। परमाणु संचालन में सैकड़ों घड़ी चक्र लग सकते हैं। तो हाँ, यह वास्तव में बहुत धीमी है।
आदिसक

2
@ Adisak कि मैंने पहली बार भ्रूण को सुना है और ऑपरेशन जोड़ें ( en.wikipedia.org/wiki/Fetch-and-add ) एक मूल वेतन वृद्धि से अधिक सैकड़ों चक्र ले सकता है। क्या आपके पास इसके लिए कोई संदर्भ है?
xaviersjs

2
@xaviersjs: stackoverflow.com/a/16132551/4238087 रजिस्टर ऑपरेशन के साथ कुछ चक्र, परमाणु के लिए 100 (100-300) चक्र फिट बैठता है। हालाँकि मेट्रिक्स 2013 से हैं, फिर भी यह विशेष रूप से मल्टी सॉकेट NUMA सिस्टम के लिए सही प्रतीत होता है।
रशियनफूल

1
कभी-कभी आपको लगता है कि आपके कोड में कोई थ्रेडिंग नहीं है ... लेकिन फिर कुछ darn लाइब्रेरी साथ आती है और इसे यू के लिए बर्बाद कर देती है। कॉन्स्ट रेफरेंस और एसटीडी :: मूव ... का उपयोग करने के लिए बेहतर है ... यदि यह स्पष्ट और स्पष्ट है कि आप कर सकते हैं .... तो पॉइंटर रेफरेंस काउंट पर भरोसा करें।
एरिक एरोनिटी

123

उपयोग करने से moveआप बढ़ने से बचते हैं, और फिर तुरंत घटते हैं, शेयरों की संख्या। हो सकता है कि आप उपयोग की संख्या पर कुछ महंगे परमाणु संचालन को बचा सकें।


1
क्या यह समय से पहले का अनुकूलन नहीं है?
YSC

11
@YSC अगर कोई इसे वहाँ नहीं रखता है तो वास्तव में इसका परीक्षण किया जाता है।
ऑरेंजडॉग

19
@YSC समयबद्धता अनुकूलन बुरा है अगर यह कोड को पढ़ने या बनाए रखने के लिए कठिन बनाता है। यह न तो कम से कम आई.एम.ओ.
Angew अब SO

17
वास्तव में। यह समय से पहले का अनुकूलन नहीं है। इसके बजाय यह फ़ंक्शन लिखने का समझदार तरीका है।
ऑर्बिट

60

मूव ऑपरेशन (जैसे मूव कंस्ट्रक्टर) सस्तेstd::shared_ptr होते हैं , क्योंकि वे मूल रूप से "चोरी करने वाले पॉइंटर्स" होते हैं (स्रोत से गंतव्य तक, अधिक सटीक होने के लिए, पूरे राज्य नियंत्रण ब्लॉक स्रोत से गंतव्य तक "चोरी" होता है, जिसमें संदर्भ गणना जानकारी भी शामिल है) ।

इसके बजाय आह्वान परमाणु संदर्भ गणना में वृद्धि पर कॉपी ऑपरेशन (यानी न केवल एक पूर्णांक डेटा सदस्य पर, बल्कि उदाहरण के लिए विंडोज पर कॉल करना), जो कि बस चोरी करने वाले बिंदुओं / राज्य की तुलना में अधिक महंगा है।std::shared_ptr++RefCountRefCountInterlockedIncrement

इसलिए, विवरण में इस मामले की रेफ गिनती गतिकी का विश्लेषण:

// shared_ptr<CompilerInvocation> sp;
compilerInstance.setInvocation(sp);

यदि आप spमूल्य से गुजरते हैं और फिर विधि के अंदर एक प्रति लेते हैं CompilerInstance::setInvocation, तो आपके पास है:

  1. जब विधि में प्रवेश, shared_ptrपैरामीटर है निर्माण कॉपी: रेफरी गिनती परमाणु वेतन वृद्धि
  2. विधि के शरीर के अंदर, आप डेटा सदस्य में पैरामीटर को कॉपी करते हैं shared_ptr: परमाणु वृद्धि की गणना करें ।
  3. जब विधि से बाहर निकलते हैं, तो shared_ptrपैरामीटर को नष्ट कर दिया जाता है: परमाणु गिनती में गिरावट

आपके पास दो परमाणु वेतन वृद्धि और एक परमाणु वृद्धि है, कुल तीन परमाणु संचालन के लिए।

इसके बजाय, यदि आप shared_ptrपैरामीटर को मान से पास करते हैं और फिर std::moveविधि के अंदर (जैसा कि क्लैंग के कोड में ठीक से किया गया है), आपके पास है:

  1. जब विधि में प्रवेश, shared_ptrपैरामीटर है निर्माण कॉपी: रेफरी गिनती परमाणु वेतन वृद्धि
  2. विधि के शरीर के अंदर, आप डेटा सदस्य में पैरामीटर: रेफरी गिनती करता नहीं बदल! आप सिर्फ पॉइंटर्स / स्टेट चुरा रहे हैं: कोई महँगा परमाणु रिफ गणना ऑपरेशन शामिल नहीं है।std::moveshared_ptr
  3. विधि से बाहर निकलने पर, shared_ptrपैरामीटर को नष्ट कर दिया जाता है; लेकिन जब से आप चरण 2 में चले गए, तब विनाश करने के लिए कुछ भी नहीं है, क्योंकि shared_ptrपैरामीटर अब कुछ भी इंगित नहीं कर रहा है। फिर, इस मामले में कोई परमाणु वृद्धि नहीं होती है।

नीचे पंक्ति: इस मामले में आपको परमाणु वृद्धि में केवल एक रेफरी काउंट मिलता है , यानी सिर्फ एक परमाणु ऑपरेशन।
जैसा कि आप देख सकते हैं, यह प्रतिलिपि मामले के लिए दो परमाणु वेतन वृद्धि और एक परमाणु वृद्धि (कुल तीन परमाणु संचालन के लिए) से बहुत बेहतर है ।


1
यह भी ध्यान देने योग्य है: वे केवल कॉन्स्टेंस संदर्भ से क्यों नहीं गुजरते हैं, और पूरे std से बचते हैं :: सामान ले जाएँ? क्योंकि पास-बाय-वैल्यू भी आपको सीधे कच्चे पॉइंटर में पास होने देता है और वहाँ सिर्फ एक share_ptr बनाया जाएगा।
जोसेफ आयरलैंड

@JosephIreland क्योंकि आप एक संदर्भ नहीं स्थानांतरित कर सकते हैं
ब्रूनो फरेरा

2
@JosephIreland क्योंकि अगर आप इसे कहते हैं compilerInstance.setInvocation(std::move(sp));तो कोई वृद्धि नहीं होगी । आप एक अधिभार जोड़कर एक ही व्यवहार प्राप्त कर सकते हैं, shared_ptr<>&&लेकिन जब आपको ज़रूरत नहीं है तो डुप्लिकेट क्यों होता है।
शाफ़्ट फ्रेक

2
@BrunoFerreira मैं अपने सवाल का जवाब दे रहा था। आपको इसे स्थानांतरित करने की आवश्यकता नहीं है क्योंकि यह एक संदर्भ है, बस इसे कॉपी करें। फिर भी दो के बजाय केवल एक ही कॉपी। कारण वे ऐसा नहीं करते हैं क्योंकि यह अनावश्यक रूप से नवनिर्मित शेयर्ड_प्टर्स की नकल करेगा, जैसे कि setInvocation(new CompilerInvocation), या शाफ़्ट के रूप में उल्लेख किया गया है setInvocation(std::move(sp))। क्षमा करें, अगर मेरी पहली टिप्पणी अस्पष्ट थी, तो मैंने वास्तव में इसे दुर्घटना से पोस्ट किया था, इससे पहले कि मैंने लिखना समाप्त कर दिया, और मैंने इसे छोड़ने का फैसला किया
जोसेफ आयरलैंड

22

shared_ptrइसकी आंतरिक स्थिति ऑब्जेक्ट पॉइंटर की प्रतिलिपि बनाना और संदर्भ गणना को बदलना शामिल करना। इसे स्थानांतरित करने से केवल आंतरिक संदर्भ काउंटर, और स्वामित्व ऑब्जेक्ट के लिए स्वैपिंग पॉइंट शामिल होते हैं, इसलिए यह तेज़ है।


16

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

एक std :: share_ptr, std :: के लिए कदम असंबद्धता से सूचक के स्वामित्व के हस्तांतरण को दर्शाता है, जबकि एक साधारण कॉपी ऑपरेशन एक अतिरिक्त मालिक को जोड़ता है। बेशक, यदि मूल मालिक बाद में अपने स्वामित्व को त्याग देता है (जैसे कि उनके std :: share_ptr को नष्ट करने की अनुमति देकर), तो स्वामित्व का एक हस्तांतरण पूरा हो गया है।

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


बिल्कुल वही, जिसकी मुझे तलाश है। आश्चर्य की बात है कि अन्य उत्तर इस महत्वपूर्ण अर्थ अंतर को कैसे नजरअंदाज करते हैं। स्मार्ट संकेत सभी स्वामित्व के बारे में है।
क्वेरुईप

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