C ++ में सामान्य पॉइंटर्स की तुलना में स्मार्ट पॉइंटर्स का ओवरहेड कितना है?


102

C ++ 11 में सामान्य पॉइंटर्स की तुलना में स्मार्ट पॉइंटर्स का ओवरहेड कितना है? दूसरे शब्दों में, अगर मैं स्मार्ट पॉइंटर्स का उपयोग करता हूं तो क्या मेरा कोड धीमा होने वाला है, और यदि हां, तो कितना धीमा है?

विशेष रूप से, मैं C ++ 11 std::shared_ptrऔर के बारे में पूछ रहा हूं std::unique_ptr

जाहिर है, स्टैक नीचे धकेल दिया गया सामान बड़ा होने वाला है (कम से कम मुझे ऐसा लगता है), क्योंकि एक स्मार्ट पॉइंटर को अपनी आंतरिक स्थिति (संदर्भ गणना, आदि) को संग्रहीत करने की भी आवश्यकता होती है, सवाल वास्तव में यह है कि यह कितना हो रहा है मेरे प्रदर्शन को प्रभावित करते हैं, अगर बिल्कुल भी?

उदाहरण के लिए, मैं एक सामान्य पॉइंटर के बजाय एक फ़ंक्शन से एक स्मार्ट पॉइंटर लौटाता हूं:

std::shared_ptr<const Value> getValue();
// versus
const Value *getValue();

या, उदाहरण के लिए, जब मेरा एक कार्य सामान्य सूचक के बजाय एक स्मार्ट पॉइंटर को पैरामीटर के रूप में स्वीकार करता है:

void setValue(std::shared_ptr<const Value> val);
// versus
void setValue(const Value *val);

8
पता करने का एकमात्र तरीका अपने कोड को बेंचमार्क करना है।
बेसिल स्टारीनेविच

आप किसकी बात कर रहे हैं? std::unique_ptrया std::shared_ptr?
स्टीफन

10
उत्तर 42 है। (एक और शब्द, जो जानता है, आपको अपने कोड को प्रोफाइल करने और अपने विशिष्ट कार्य भार के लिए अपने हार्डवेयर पर समझने की आवश्यकता है।)
निम

आपके एप्लिकेशन को महत्वपूर्ण होने के लिए स्मार्ट पॉइंटर्स का अत्यधिक उपयोग करने की आवश्यकता है।
user2672165

एक साधारण सेटर फंक्शन में शेअर_प्ट्र का उपयोग करने की लागत बहुत ही भयानक है और इसमें 100% ओवरहेड जोड़ा जाएगा।
लोथर

जवाबों:


178

std::unique_ptr स्मृति ओवरहेड है केवल अगर आप इसे कुछ गैर तुच्छ deleter के साथ प्रदान करते हैं।

std::shared_ptr हमेशा संदर्भ काउंटर के लिए मेमोरी ओवरहेड है, हालांकि यह बहुत छोटा है।

std::unique_ptr केवल कंस्ट्रक्टर के दौरान ओवरहेड किया जाता है (यदि उसे प्रदान किए गए डेलेटर और / या अशक्त-सूचक को कॉपी करना है) और विध्वंसक के दौरान (स्वामित्व वाली वस्तु को नष्ट करने के लिए)।

std::shared_ptrकंस्ट्रक्टर में समय ओवरहेड (संदर्भ काउंटर बनाने के लिए), विध्वंसक में (संदर्भ काउंटर को कम करने और संभवतः ऑब्जेक्ट को नष्ट करने के लिए) और असाइनमेंट ऑपरेटर (संदर्भ काउंटर को बढ़ाने के लिए) में है। थ्रेड-सेफ्टी गारंटी के कारण std::shared_ptr, ये इंक्रीमेंट्स / डीक्रोमेंट परमाणु होते हैं, इस प्रकार कुछ और ओवरहेड जोड़ते हैं।

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

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


11
unique_ptrविध्वंसक में कोई उपरि नहीं है। यह ठीक वैसा ही होता है जैसा आप कच्चे पॉइंटर से करते हैं।
आर। मार्टिनो फर्नांडिस

6
@ R.MartinhoFernandes कच्चे सूचक की तुलना में, यह विध्वंसक में ओवरहेड होता है, क्योंकि कच्चा सूचक विध्वंसक कुछ भी नहीं करता है। कच्चे सूचक का उपयोग कैसे किया जाएगा, इसकी तुलना में, यह निश्चित रूप से कोई उपरि नहीं है।
lisyarus

3
वर्थ सुरक्षा / विनाश / असाइनमेंट लागत के उस हिस्से पर ध्यान देने की वजह थ्रेड सेफ्टी के कारण है
जो

1
इसके अलावा, डिफ़ॉल्ट निर्माता के बारे में क्या std::unique_ptr? यदि आप एक निर्माण करते हैं std::unique_ptr<int>, तो आंतरिक int*को nullptrयह पसंद है कि आप इसे पसंद करते हैं या नहीं।
मार्टिन डोज़र्डिक

1
@MartinDrozdik अधिकांश स्थितियों में आप कच्चे पॉइंटर को भी शून्य कर देंगे, यह जांचने के लिए कि यह बाद में अशक्त है, या ऐसा कुछ। फिर भी, इस जवाब में जोड़ा, धन्यवाद।
सिंहपर्णी

26

सभी कोड के प्रदर्शन के साथ के रूप में, केवल वास्तव में विश्वसनीय साधन हार्ड जानकारी प्राप्त करने के लिए है को मापने और / या निरीक्षण मशीन ।

उस ने कहा, सरल तर्क है कि कहते हैं

  • आप डिबग बिल्ड में कुछ ओवरहेड की अपेक्षा कर सकते हैं, उदाहरण के लिए operator->एक फ़ंक्शन कॉल के रूप में निष्पादित किया जाना चाहिए ताकि आप इसमें कदम रख सकें (यह बदले में वर्गों और गैर-डीबग के रूप में कार्यों के लिए समर्थन की सामान्य कमी के कारण है)।

  • के लिए shared_ptrआप, प्रारंभिक निर्माण में कुछ भूमि के ऊपर उम्मीद कर सकते हैं के बाद से है कि एक नियंत्रण ब्लॉक के गतिशील आवंटन शामिल है, और गतिशील आवंटन बहुत C ++ किसी अन्य बुनियादी आपरेशन की तुलना में धीमी है (उपयोग करना make_sharedहै कि भूमि के ऊपर कम करने के लिए जब व्यावहारिक रूप से संभव,)।

  • इसके अलावा shared_ptrसंदर्भ संख्या को बनाए रखने में कुछ न्यूनतम ओवरहेड है, उदाहरण के लिए जब एक shared_ptrमूल्य से गुजर रहा है , लेकिन इसके लिए ऐसा कोई ओवरहेड नहीं है unique_ptr

पहले बिंदु को ध्यान में रखते हुए, जब आप मापते हैं, तो यह करें कि डिबग और रिलीज़ दोनों के लिए बनाता है।

अंतरराष्ट्रीय सी ++ मानकीकरण समिति ने प्रदर्शन पर एक तकनीकी रिपोर्ट प्रकाशित की है , लेकिन यह 2006 में था, इससे पहले unique_ptrऔर shared_ptrमानक पुस्तकालय में जोड़ा गया था। फिर भी, स्मार्ट पॉइंटर्स उस बिंदु पर पुरानी टोपी थे, इसलिए रिपोर्ट ने उस पर भी विचार किया। प्रासंगिक भाग का हवाला देते हुए:

"अगर एक सामान्य स्मार्ट पॉइंटर के माध्यम से इसे एक्सेस करने की तुलना में एक तुच्छ स्मार्ट पॉइंटर के माध्यम से एक मूल्य तक पहुंच धीमी है, तो कंपाइलर अक्षमता से अमूर्त को संभाल रहा है। अतीत में, अधिकांश संकलक के पास महत्वपूर्ण अमूर्त दंड था और कई वर्तमान संकलक अभी भी करते हैं। हालांकि, कम से कम दो संकलक को 1% से कम अमूर्त दंड और 3% का एक अन्य दंड दिया गया है, इसलिए इस तरह के ओवरहेड को खत्म करना कला की स्थिति के भीतर अच्छी तरह से है ”

एक सूचित अनुमान के रूप में, "कला के राज्य के भीतर कुएं" को आज 2014 की शुरुआत तक सबसे लोकप्रिय संकलक के साथ हासिल किया गया है।


क्या आप कृपया मेरे प्रश्न में जोड़े गए मामलों के बारे में अपने उत्तर में कुछ विवरण शामिल कर सकते हैं?
वेनेमो

यह 10 या उससे अधिक साल पहले सच हो सकता है, लेकिन आज, मशीन कोड का निरीक्षण उतना उपयोगी नहीं है जितना कि ऊपर दिए गए व्यक्ति का सुझाव है। कैसे पाइपलाइज्ड, वेक्टराइज्ड, निर्देश दिए गए हैं, इस पर निर्भर करता है ... और कंपाइलर / प्रोसेसर सट्टा से कैसे निपटता है यह आखिर कितना तेज है। कम कोड मशीन कोड जरूरी नहीं कि तेज कोड हो। प्रदर्शन को निर्धारित करने का एकमात्र तरीका इसे प्रोफाइल करना है। यह एक प्रोसेसर के आधार पर और प्रति संकलक में भी बदल सकता है।
बाय

एक मुद्दा मैंने देखा है कि, एक बार साझा किए गए_पार्ट्स का उपयोग सर्वर में किया जाता है, तो शेयर्ड_प्टर्स का उपयोग शुरू हो जाता है, और जल्द ही शेयर्ड_प्टर्स डिफ़ॉल्ट मेमोरी मैनेजमेंट तकनीक बन जाते हैं। इसलिए अब आपने 1-3% अमूर्त दंड दोहराया है जो बार-बार लिया जाता है।
नाथन डोरोमल

मुझे लगता है कि डिबग बिल्ड का बेंचमार्किंग समय की पूरी और पूरी तरह बर्बादी है
पॉल चिल्ड्स

26

मेरा जवाब दूसरों से अलग है और मुझे आश्चर्य है कि क्या उन्होंने कभी कोड को प्रोफाइल किया है।

share_ptr के पास निर्माण के लिए एक महत्वपूर्ण ओवरहेड है क्योंकि यह नियंत्रण ब्लॉक के लिए मेमोरी आवंटन है (जो सभी कमजोर संदर्भों के लिए रेफरी काउंटर और एक पॉइंटर सूची रखता है)। इसका एक बड़ा मेमोरी ओवरहेड भी है क्योंकि इस तथ्य और तथ्य यह है कि std :: share_ptr हमेशा एक 2 पॉइंटर टपल (ऑब्जेक्ट पर एक, कंट्रोल ब्लॉक में एक) होता है।

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

यही कारण है कि आपको ऐसा नहीं करना चाहिए जब तक कि फ़ंक्शन स्वामित्व प्रबंधन में वास्तव में शामिल न हो। अन्यथा "share_ptr.get ()" का उपयोग करें। यह सुनिश्चित करने के लिए डिज़ाइन नहीं किया गया है कि एक सामान्य फ़ंक्शन कॉल के दौरान आपकी ऑब्जेक्ट को नहीं मारा गया है।

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

यह एक चांदी की गोली नहीं है और कच्चे संकेत या तो परिभाषा से खराब नहीं हैं। खराब प्रोग्रामर खराब हैं और खराब डिजाइन खराब है। देखभाल के साथ डिजाइन, मन में स्पष्ट स्वामित्व के साथ डिजाइन और सबसिस्टम एपीआई सीमा पर साझा करने की कोशिश करें।

यदि आप अधिक जानना चाहते हैं तो आप निकोलई एम। जोसुटिस को "सी + + में साझा पॉइंटर्स की वास्तविक कीमत" के बारे में अच्छी बात देख सकते हैं। https://vimeo.com/131189627
यह कार्यान्वयन विवरण और सीपीयू आर्किटेक्चर में राइट्स बैरियर, परमाणु ताले आदि को एक बार सुनने के बाद आप कभी इस सुविधा के सस्ते होने की बात नहीं करेंगे। यदि आप केवल परिमाण धीमे होने का प्रमाण चाहते हैं, तो पहले 48 मिनट को छोड़ दें और उसे उदाहरण कोड चलाते हुए देखें, जो हर जगह साझा सूचक का उपयोग करते समय 180 गुना धीमा (-O3 के साथ संकलित) तक चलता है।


आपके उत्तर के लिए धन्यवाद! आपने किस प्लेटफॉर्म पर प्रोफाइल बनाया है? क्या आप कुछ डेटा के साथ अपने दावों का समर्थन कर सकते हैं?
वेनमो

मेरे पास दिखाने के लिए कोई संख्या नहीं है, लेकिन आप नीको जोसटिस बात में कुछ पा सकते हैं vimeo.com/131189627
लोथर

6
कभी सुना है std::make_shared()? इसके अलावा, मुझे लगता है कि ज़बरदस्त दुरुपयोग का प्रदर्शन थोड़ा उबाऊ हो रहा है ...
डेडप्लिकेटर

2
सभी "मेक_शेयर" कर सकते हैं जो आपको एक अतिरिक्त आबंटन से सुरक्षित करता है और यदि नियंत्रण ब्लॉक को ऑब्जेक्ट के सामने आवंटित किया जाता है तो आपको थोड़ा और कैशे इलाका देना होगा। जब आप पॉइंटर को पास करते हैं तो यह बिल्कुल भी मदद नहीं कर सकता है। यह समस्याओं की जड़ नहीं है।
लोथर

14

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

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

आपके कोड को धीमा बनाने वाली खोजों, अनावश्यक लूप प्रोसेसिंग, डेटा की विशाल प्रतियों और डिस्क पर बहुत सारे लिखने के संचालन (जैसे सैकड़ों) होंगे।

एक स्मार्ट पॉइंटर के फायदे प्रबंधन से संबंधित हैं। लेकिन क्या ओवरहेड आवश्यक है? यह आपके कार्यान्वयन पर निर्भर करता है। मान लीजिए कि आप 3 चरणों की एक सरणी पर पुनरावृत्ति कर रहे हैं, प्रत्येक चरण में 1024 तत्वों की एक सरणी है। smart_ptrइस प्रक्रिया के लिए बनाना ओवरकिल हो सकता है, क्योंकि एक बार पुनरावृत्ति हो जाने के बाद आपको पता चल जाएगा कि आपको इसे मिटाना है। तो आप एक का उपयोग नहीं करने से अतिरिक्त स्मृति प्राप्त कर सकते हैं smart_ptr...

लेकिन क्या आप वास्तव में ऐसा करना चाहते हैं?

एक एकल मेमोरी रिसाव आपके उत्पाद को समय में विफलता का बिंदु बना सकता है (मान लें कि आपका कार्यक्रम प्रत्येक घंटे 4 मेगाबाइट लीक करता है, तो कंप्यूटर को तोड़ने में महीनों लग जाएंगे, फिर भी, यह टूट जाएगा, आपको पता है क्योंकि रिसाव है) ।

यह कहने जैसा है कि "आपको सॉफ़्टवेयर की गारंटी 3 महीने के लिए है, फिर, मुझे सेवा के लिए कॉल करें।"

तो अंत में यह वास्तव में एक मामला है ... क्या आप इस जोखिम को संभाल सकते हैं? सैकड़ों अलग-अलग वस्तुओं पर अपनी अनुक्रमण को संभालने के लिए एक कच्चे सूचक का उपयोग करना स्मृति के नियंत्रण को खोने के लायक है।

यदि उत्तर हां है, तो एक कच्चे सूचक का उपयोग करें।

यदि आप भी इस पर विचार नहीं करना चाहते हैं, तो यह smart_ptrएक अच्छा, व्यवहार्य और भयानक समाधान है।


4
ठीक है, लेकिन संभव मेमोरी लीक की जांच करने में वेलग्रिंड अच्छा है, इसलिए जब तक आप इसका उपयोग करते हैं, तब तक आपको सुरक्षित होना चाहिए ™
ग्रेवुल्फ

@Paladin हाँ, यदि आप अपनी स्मृति को संभाल सकते हैं, तो smart_ptrवास्तव में बड़ी टीमों के लिए उपयोगी हैं
क्लाउडीगार्ड्स

3
मैं Unique_ptr का उपयोग करता हूं, यह बहुत सी चीजों को सरल करता है, लेकिन साझा_ptr को पसंद नहीं करता है, संदर्भ गिनती बहुत कुशल नहीं है GC और इसके सही नहीं है
ग्रेवुल्फ़

1
@Paladin मैं कच्चे पॉइंटर्स का उपयोग करने की कोशिश करता हूं अगर मैं सब कुछ एनकैप कर सकता हूं। अगर यह कुछ ऐसा है कि मैं एक तर्क की तरह हर जगह से गुजर रहा हूं तो शायद मैं एक स्मार्ट_प्ट्र पर विचार करूंगा। मेरे अधिकांश यूनिक_प्टर्स का उपयोग बड़े कार्यान्वयन में किया जाता है, मुख्य या रन विधि की तरह
क्लाउडीगर्ग

@ लोथर मैं देख रहा हूँ कि मैंने आपके उत्तर में कही गई बातों में से एक को परास्त कर दिया है: Thats why you should not do this unless the function is really involved in ownership management... महान उत्तर, धन्यवाद,
उत्थित

0

केवल एक झलक के लिए और सिर्फ []ऑपरेटर के लिए, यह कच्चे पॉइंटर की तुलना में ~ 5X धीमा है जैसा कि निम्नलिखित कोड में दिखाया गया है, जिसे gcc -lstdc++ -std=c++14 -O0इस परिणाम का उपयोग करके और आउटपुट किया गया था :

malloc []:     414252610                                                 
unique []  is: 2062494135                                                
uq get []  is: 238801500                                                 
uq.get()[] is: 1505169542
new is:        241049490 

मैं c ++ सीखना शुरू कर रहा हूं, मेरे दिमाग में यह आया: आपको हमेशा यह जानने की जरूरत है कि आप क्या कर रहे हैं और यह जानने के लिए अधिक समय लेते हैं कि दूसरों ने आपके c ++ में क्या किया है।

संपादित करें

@ मोहन कुमार के अनुसार, मैंने और अधिक जानकारी प्रदान की। Gcc संस्करण है 7.4.0 (Ubuntu 7.4.0-1ubuntu1~14.04~ppa1), उपरोक्त परिणाम तब प्राप्त -O0किया जाता है जब इसका उपयोग किया जाता है, हालांकि, जब मैं '-O2' ध्वज का उपयोग करता हूं, तो मुझे यह मिला:

malloc []:     223
unique []  is: 105586217
uq get []  is: 71129461
uq.get()[] is: 69246502
new is:        9683

तब में स्थानांतरित कर दिया clang version 3.9.0, -O0था:

malloc []:     409765889
unique []  is: 1351714189
uq get []  is: 256090843
uq.get()[] is: 1026846852
new is:        255421307

-O2 था:

malloc []:     150
unique []  is: 124
uq get []  is: 83
uq.get()[] is: 83
new is:        54

क्लैंग -O2का परिणाम आश्चर्यजनक है।

#include <memory>
#include <iostream>
#include <chrono>
#include <thread>

uint32_t n = 100000000;
void t_m(void){
    auto a  = (char*) malloc(n*sizeof(char));
    for(uint32_t i=0; i<n; i++) a[i] = 'A';
}
void t_u(void){
    auto a = std::unique_ptr<char[]>(new char[n]);
    for(uint32_t i=0; i<n; i++) a[i] = 'A';
}

void t_u2(void){
    auto a = std::unique_ptr<char[]>(new char[n]);
    auto tmp = a.get();
    for(uint32_t i=0; i<n; i++) tmp[i] = 'A';
}
void t_u3(void){
    auto a = std::unique_ptr<char[]>(new char[n]);
    for(uint32_t i=0; i<n; i++) a.get()[i] = 'A';
}
void t_new(void){
    auto a = new char[n];
    for(uint32_t i=0; i<n; i++) a[i] = 'A';
}

int main(){
    auto start = std::chrono::high_resolution_clock::now();
    t_m();
    auto end1 = std::chrono::high_resolution_clock::now();
    t_u();
    auto end2 = std::chrono::high_resolution_clock::now();
    t_u2();
    auto end3 = std::chrono::high_resolution_clock::now();
    t_u3();
    auto end4 = std::chrono::high_resolution_clock::now();
    t_new();
    auto end5 = std::chrono::high_resolution_clock::now();
    std::cout << "malloc []:     " <<  (end1 - start).count() << std::endl;
    std::cout << "unique []  is: " << (end2 - end1).count() << std::endl;
    std::cout << "uq get []  is: " << (end3 - end2).count() << std::endl;
    std::cout << "uq.get()[] is: " << (end4 - end3).count() << std::endl;
    std::cout << "new is:        " << (end5 - end4).count() << std::endl;
}

मैंने अभी कोड का परीक्षण किया है, यह केवल अद्वितीय संकेतक का उपयोग करते समय केवल 10% धीमा है।
मोहन कुमार

8
कभी भी -O0या डिबग कोड के साथ बेंचमार्क न करें । आउटपुट बेहद अक्षम होगा । हमेशा कम से कम -O2(या -O3आजकल का उपयोग करें क्योंकि कुछ -O2
वैश्वीकरण

1
यदि आपके पास समय है और एक कॉफी ब्रेक टेक-ओ 4 चाहते हैं तो लिंक समय अनुकूलन प्राप्त करें और सभी छोटे छोटे अमूर्त कार्यों को इनलाइन और गायब हो जाते हैं।
लोथर

आपको freeमॉलॉक टेस्ट में एक कॉल शामिल करना चाहिए , और delete[]नए (या वैरिएबल aस्टैटिक बनाने के लिए), क्योंकि unique_ptrएस delete[]हुड के तहत कॉल कर रहे हैं , उनके डिस्ट्रक्टर्स में।
RnMss
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.