(स्थैतिक) C ++ कार्यक्रमों में इष्टतम कंटेनरों का चयन

नमस्ते आज मैं स्थैतिक विश्लेषण के बारे में फिर से बात करना चाहूंगा। और फिर से सी ++ के बारे में। केवल, पीवीएस-स्टूडियो के विपरीत, हम अपने कार्यक्रमों में किसी भी त्रुटि की तलाश नहीं करेंगे (हालांकि वे न केवल त्रुटियों के लिए दिखते हैं), बल्कि उन स्थानों के लिए जो कि पर्याप्त रूप से पर्याप्त नहीं लिखे गए हैं। और इन स्थानों में से एक कार्यक्रम में डेटा के लिए एक कंटेनर का चयन कर रहा है। अगर मुझे आपकी दिलचस्पी है, तो बिल्ली में आपका स्वागत है!

समस्या


कोरहार्ड 2018 शरद ऋतु में (एक बहुत अच्छा सम्मेलन, आओ) मैंने इस बारे में बात की कि कैसे सी ++ संकलक इस समय अच्छी तरह से अनुकूलन नहीं करते हैं। और मेरी एक शिकायत यह थी कि कंपाइलर हमारे कार्यक्रमों में कंटेनरों के उपयोग का अनुकूलन नहीं कर सकते। आइए कुछ कोड उदाहरण देखें।

void foo() { std::vector<int> v(42); } 

ऐसा लगता है कि इस तरह के एक साधारण मामले में, कंपाइलर को इस फ़ंक्शन को ऑप्टिमाइज़ करने में सक्षम होना चाहिए और केवल std :: वेक्टर प्रकार के एक चर घोषणा को बाहर फेंकना चाहिए, क्योंकि C ++ 14 से शुरू होने पर कंपाइलर को डायनामिक मेमोरी आवंटन को हटाने की अनुमति है, लेकिन कंपाइलर नहीं करता है। इसका कारण यह है कि फिलहाल गतिशील आवंटन को हटाने के लिए केवल एक C ++ कंपाइलर अनुकूलन लागू होता है - क्लैंग। अन्य सभी कंपाइलर अभी तक यह नहीं जानते हैं कि यह कैसे करना है। लेकिन क्लैंग भी सीमित संख्या में मामलों में ऐसा कर सकता है।

इस स्थिति में, हम std :: वेक्टर को std :: array से बदल सकते हैं, बशर्ते कि चयनित वेक्टर का आकार बहुत बड़ा न हो, क्योंकि हमारे पास इस तरह के प्रतिस्थापन के लिए पर्याप्त स्टैक नहीं हो सकता है। इस तरह के प्रतिस्थापन से ढेर में एक महंगी स्मृति आवंटन को हटा दिया जाएगा, और प्लस यह है कि जब std :: array का उपयोग किया जाता है, तो कंपाइलर पहले से ही std :: array को फंक्शन से पूरी तरह से फेंक सकता है!

यदि हम प्रदर्शन अनुकूलन के बारे में बात कर रहे हैं, तो हम निम्नलिखित उदाहरण पर विचार करने का प्रस्ताव करते हैं:

 void foo() { std::vector<int> v; for(int i = 0; i < 42; ++i) { v.insert(v.begin(), 42); } for(const auto val : v) { std::cout << val << ' '; } } 

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

आइए एक और उदाहरण कोड देखें:

 void foo() { std::list<int> v; for(int i = 0; i < 3; ++i) { v.push_front(i); } for(const auto val : v) { std::cout << val << ' '; } } 

इस मामले में, हम देख सकते हैं कि हम दर्द रहित रूप से std :: list (हाँ, मुझे पता है कि कुछ लोग इसका उपयोग करते हैं) को std :: Forward_list से बदल सकते हैं। इस मामले में, इस मामले में, हम पूरी तरह से कुछ भी नहीं खो देंगे, लेकिन हम स्मृति बचत प्राप्त करेंगे। स्वाभाविक रूप से, संकलक अब ऐसा अनुकूलन नहीं करता है।

निम्नलिखित उदाहरण में एक समान चाल की जा सकती है:

 void foo() { std::deque<int> v; for(int i = 0; i < 3; ++i) { v.push_back(i); } for(int i = 0; i < 3; ++i) { std::cout << v.back() << ' '; v.pop_back(); } } 

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

वैसे, मुझे लगता है कि पर्याप्त उदाहरण हैं। आप में से प्रत्येक भी उनमें से बहुत से आ सकते हैं।

प्रयुक्त साधन


स्थैतिक विश्लेषक को लागू करने के लिए, मैंने क्लैंग स्टेटिक एनाल्जेसर (CSA) और क्लैंग टाइड का उपयोग किया, जो एलएलवीएम पैकेज का हिस्सा हैं। मैंने इन उपकरणों को चुना, क्योंकि मैं उन्हें स्थैतिक विश्लेषण के लिए खुले उपकरणों में सबसे आशाजनक मानता हूं। इसके अलावा, क्लैंग उच्चतम-गुणवत्ता वाले C ++ पार्सर्स में से एक प्रदान करता है, जो अन्य स्टैटिक विश्लेषणकर्ता (जब तक कि वे libclang का उपयोग नहीं करते हैं) का दावा नहीं कर सकते।

CSA और Clang Tidy, दोनों स्टैटिस्टिकल एनालिस्ट हैं, दोनों ही LLVM का हिस्सा हैं। अंतर क्या है? अंतर यह है कि Clang Tidy को सरल चेक लिखने के लिए डिज़ाइन किया गया है, जो मूल रूप से अमूर्त सिंटैक्स ट्री पर किसी प्रकार का पैटर्न खोजने में शामिल है, कुछ प्रकार की चेतावनी प्रदर्शित करता है, और संभवतः इसे दूसरे के साथ स्वचालित रूप से प्रतिस्थापित करता है। आप यहां क्लैंग टाइड के बारे में अधिक जान सकते हैं।

CSA को अधिक "गंभीर" और संसाधन-गहन (दोनों कार्यान्वयन के दृष्टिकोण से, और निष्पादन समय / स्मृति खर्च किए गए दृष्टिकोण से) चेक लिखने के लिए डिज़ाइन किया गया है। उदाहरण के लिए, एक प्रतीकात्मक निष्पादन तंत्र उपलब्ध है।

मैंने CSA में चेक को लागू करने का निर्णय लिया, क्योंकि यह मेरे लिए सामान्य नहीं लगता, इसके अलावा, भविष्य में यह कठिन और कठिन हो जाएगा। और इसे क्लैंग टायडी के माध्यम से चलाने का निर्णय लिया गया, क्योंकि इस स्थिर विश्लेषक के पास विभिन्न आईडीई के साथ कई एकीकरण हैं।

हम समस्याओं को हल करने के लिए कैसे (प्रयास) करेंगे


शुरू करने के लिए, यह काफी मजबूत प्रतिबंधों के एक जोड़े को पेश करने के लायक है, जो मुख्य रूप से इस तथ्य से संबंधित हैं कि यह सिर्फ एक प्रोटोटाइप है:

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

अब प्रोटोटाइप निम्नलिखित सरल एल्गोरिथ्म को लागू करता है:

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

फिलहाल कंटेनर परिचालन का वर्गीकरण इस प्रकार है (भविष्य में इसका विस्तार किया जाएगा):

  • एक आइटम कंटेनर के शीर्ष पर जोड़ें।
  • कंटेनर के बीच में एक आइटम जोड़ना।
  • कंटेनर के अंत में एक आइटम जोड़ना।
  • कंटेनर की शुरुआत से एक आइटम को निकालना।
  • कंटेनर के बीच से कोई आइटम निकालना।
  • कंटेनर के अंत से एक आइटम निकाल रहा है।

इस समय वर्गीकरण अपूर्ण है और यहां तक ​​कि इस सूची में भी सही ढंग से काम नहीं करता है। उदाहरण के लिए, सम्मिलित ऑपरेशन, भले ही यह शुरुआत में किया जाता है, विश्लेषक बीच में डालने के रूप में वर्गीकृत करता है, हालांकि वास्तव में यह बिल्कुल भी नहीं है।

झूठी सकारात्मकता से लड़ना


किसी भी स्थिर विश्लेषण में, झूठी सकारात्मकता मुख्य सिरदर्द है। यदि उनमें से बहुत सारे हैं, तो उपयोगी संदेश कचरे में खो जाते हैं। इसलिए, इस मामले में, आपको बहुत रूढ़िवादी तरीके से कार्य करना होगा और केवल उन मामलों में चेतावनी जारी करनी चाहिए जहां हम वास्तव में हमारे निदान में विश्वास करते हैं और यह कह सकते हैं कि कोड में कुछ जगह पर वास्तव में कुछ गलत है।

यदि हम संकलक अनुकूलन के बारे में बात कर रहे हैं, तो यह अभी भी वहाँ दुखी है - समुचित अनुकूलन C ++ मानक के अनुसार कार्यक्रम के व्यवहार को नहीं बदल सकता है (अन्यथा, ऐसा एक अनुकूलक बेकार है)। और अनुकूलन को निराशावाद का परिचय नहीं देना चाहिए :) तो यहां आपको अपने निर्णयों में बहुत अधिक सावधानी बरतनी होगी।

इस विश्लेषक में, इस संघर्ष का परिणाम है कि यदि विश्लेषक देखता है कि वर्तमान में कुछ असमर्थित संचालन किया जा रहा है, तो इस कंटेनर के लिए विश्लेषण बंद कर दिया गया है।

नुकसान और संभव समाधान


इस पद्धति के साथ कई समस्याएं हैं।

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

 void foo(int* ptr, std::vector<int>& v) { if(ptr == nullptr) { v.insert(v.begin(), 42); } else { v.push_back(84); } } 

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

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

समस्या को हल करने के लिए अलग-अलग तरीके हैं।

पहला यह है कि उपयोगकर्ता अपने कंटेनरों को किसी तरह से एनोटेट करता है (एक विशेष प्रकार की टिप्पणी, सी ++ विशेषताएँ, कुछ और)। इस पद्धति के साथ समस्या यह है कि हमें यह समझने की आवश्यकता है कि सामान्य रूप से कैसे एनोटेट किया जाए, गुणात्मक विश्लेषण के लिए हमें कौन सी जानकारी की आवश्यकता है। एक और समस्या खुद कंटेनरों का कोड संशोधन हो सकता है, जो हमेशा संभव नहीं होता है।

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

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

संभव अनुप्रयोग


मैं इस प्रकार के विश्लेषण के लिए कई एप्लिकेशन देखता हूं:

  1. एक स्थिर विश्लेषक की तरह। यहां सब कुछ सरल है - स्थैतिक विश्लेषण का एक और परीक्षण, जिसे आप अपने दिल की इच्छाओं के रूप में चलाते हैं (अपने हाथों से, विकास के दौरान आईडीई में स्वचालित रूप से, सीआई आदि पर), जहां आपको संभवतः संकेत दिया जाएगा कि कहीं आप कर सकते हैं एक कंटेनर और बेहतर उठाओ।
  2. जैसे कंपाइलर में ऑप्टिमाइजेशन। कुछ मामलों में, हम गारंटी दे सकते हैं कि कंटेनर की जगह निश्चित रूप से प्रदर्शन को नकारात्मक रूप से प्रभावित नहीं करेगी। उदाहरण के लिए, std की जगह: छोटे आकार के लिए वेक्टर, जिसे std के साथ संकलित समय पर जाना जाता है :: सरणी या स्थान std :: std के साथ सूची :: forward_list जब हमें द्विभाजन की आवश्यकता नहीं होती है और हम सूची से आकार नहीं लेते हैं। संकलक कंटेनरों को हमारे ज्ञान के बिना अधिक इष्टतम लोगों के साथ बदल सकता है, क्योंकि यह पहले से ही बहुत बड़ी संख्या में चीजों के लिए करता है।
  3. एक गतिशील विश्लेषक की तरह। यह वह दिशा है जो मुझे इस प्रकार के विश्लेषण के लिए सबसे अधिक आशाजनक लगती है। वास्तव में, कार्यक्रम निष्पादन प्रोफ़ाइल के बारे में ज्ञान की मदद से, हम, उदाहरण के लिए, प्रत्येक कोड शाखा निष्पादन की संभावनाओं के रूप में हमारे लिए ऐसी महत्वपूर्ण जानकारी प्राप्त कर सकते हैं। और यह अधिक सटीक मूल्यांकन के लिए आवश्यक है। और इस तरह के एक विश्लेषण के साथ, आप पहले से ही पीजीओ के साथ एकीकरण की दिशा में सोच सकते हैं ...

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

समान दिशाओं में काम करें


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

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

मैं SETL पर विभिन्न कार्यों (वहाँ कई हैं) को देखने की सलाह देता हूं । उनमें, लेखकों ने अक्सर कंटेनर के स्वत: चयन के बारे में भी सवाल उठाए।

संदर्भ


  1. जीथब पर वर्तमान कार्यान्वयन
  2. सी ++ रूस 2017: यूरी एफिमोचेव, क्लैंग-टिड्डी: सी ++ सार सिंटांग ट्री के अंदर एक यात्रा
  3. गिरगिट: सामूहिक का अनुकूल चयन
  4. क्लैंग स्टैटिक विश्लेषक गाइड
  5. टेलीग्राम में संकलक के विकास पर रूसी-भाषा चैट । यदि आप रुचि रखते हैं, तो आइए, यह बहुत दिलचस्प है। बस बाढ़ से सावधान रहें - वे तुरंत उसे दंडित करेंगे :)

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

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

आपका ध्यान के लिए धन्यवाद!

Source: https://habr.com/ru/post/hi455379/


All Articles