मैं एसटीएपीएल परियोजना पर काम करता हूं जो कि भारी-भरकम सी ++ लाइब्रेरी है। एक बार थोड़ी देर में, हमें संकलन समय को कम करने के लिए सभी तकनीकों को फिर से देखना होगा। यहाँ, मैंने उन तकनीकों का सार प्रस्तुत किया है जिनका हम उपयोग करते हैं। इन तकनीकों में से कुछ पहले से ही ऊपर सूचीबद्ध हैं:
सबसे अधिक समय लेने वाले वर्गों का पता लगाना
हालाँकि, प्रतीक की लंबाई और संकलन समय के बीच कोई सिद्ध संबंध नहीं है, हमने देखा है कि छोटे औसत प्रतीक आकार सभी संकलनकर्ताओं पर संकलन समय में सुधार कर सकते हैं। तो आपका पहला लक्ष्य यह है कि आप अपने कोड में सबसे बड़े प्रतीकों को खोजें।
विधि 1 - आकार के आधार पर प्रतीकों को क्रमबद्ध करें
आप nmउनके आकार के आधार पर प्रतीकों को सूचीबद्ध करने के लिए कमांड का उपयोग कर सकते हैं:
nm --print-size --size-sort --radix=d YOUR_BINARY
इस कमांड में --radix=d आप दशमलव संख्या में आकार देख सकते हैं (डिफ़ॉल्ट हेक्स है)। अब सबसे बड़े प्रतीक को देखकर, पहचानें कि क्या आप संबंधित वर्ग को तोड़ सकते हैं और इसे गैर-टेम्प्लेटेड भागों को आधार वर्ग में विभाजित करके, या कक्षा को कई वर्गों में विभाजित करके इसे फिर से डिज़ाइन करने का प्रयास कर सकते हैं।
विधि 2 - लंबाई के आधार पर प्रतीकों को क्रमबद्ध करें
आप नियमित nmकमांड को चला सकते हैं और अपनी पसंदीदा स्क्रिप्ट ( AWK , पायथन , आदि) को पाइप को उनकी लंबाई के आधार पर सॉर्ट करने के लिए पाइप कर सकते हैं । हमारे अनुभव के आधार पर, यह विधि उम्मीदवारों को पद्धति 1 से बेहतर बनाने में सबसे बड़ी परेशानी की पहचान करती है।
विधि 3 - टेंपलेट का उपयोग करें
" Templight एक है बजना आधारित समय और टेम्पलेट instantiations की स्मृति की खपत प्रोफ़ाइल और टेम्पलेट इन्स्टेन्शियशन प्रक्रिया में आत्मनिरीक्षण हासिल करने के लिए इंटरैक्टिव डिबगिंग सत्र प्रदर्शन करने के लिए उपकरण"।
आप बाहर की जाँच करके Templight स्थापित कर सकते हैं LLVM और ( निर्देशों ) की और उस पर Templight पैच लागू । एलएलवीएम और क्लैंग के लिए डिफ़ॉल्ट सेटिंग डिबग और अभिकथन पर है, और ये आपके संकलन समय को महत्वपूर्ण रूप से प्रभावित कर सकते हैं। ऐसा लगता है जैसे टेंपलाइट को दोनों की आवश्यकता है, इसलिए आपको डिफ़ॉल्ट सेटिंग्स का उपयोग करना होगा। एलएलवीएम और क्लैंग को स्थापित करने की प्रक्रिया में लगभग एक घंटे का समय लगना चाहिए।
पैच को लागू करने के बाद आप templight++अपने कोड को संकलित करने के लिए स्थापना पर निर्दिष्ट बिल्ड फ़ोल्डर में स्थित का उपयोग कर सकते हैं ।
सुनिश्चित करें कि templight++आपके पेट में है। अब निम्नलिखित स्विच को अपने संकलन में संकलित करेंCXXFLAGS अपने मेकफाइल में या अपने कमांड लाइन विकल्प करें:
CXXFLAGS+=-Xtemplight -profiler -Xtemplight -memory -Xtemplight -ignore-system
या
templight++ -Xtemplight -profiler -Xtemplight -memory -Xtemplight -ignore-system
संकलन हो जाने के बाद, आपके पास .trace.memory.pbf और .trace.pbf एक ही फ़ोल्डर में उत्पन्न होंगे। इन निशानों की कल्पना करने के लिए, आप टेम्प्लाईट टूल्स का उपयोग कर सकते हैं जो इन्हें अन्य प्रारूपों में बदल सकते हैं। इन निर्देशों का पालन करेंTemplight-Convert स्थापित करने के लिए । हम आमतौर पर callgrind आउटपुट का उपयोग करते हैं। यदि आपका प्रोजेक्ट छोटा है तो आप ग्राफविज़ आउटपुट का भी उपयोग कर सकते हैं:
$ templight-convert --format callgrind YOUR_BINARY --output YOUR_BINARY.trace
$ templight-convert --format graphviz YOUR_BINARY --output YOUR_BINARY.dot
उत्पन्न कॉलग्रिंड फ़ाइल का उपयोग करके खोला जा सकता है kcachegrind जिसमें आप सबसे अधिक समय / स्मृति लेने वाली इन्स्टेन्शियशन ट्रेस कर सकते हैं।
टेम्प्लेट इंस्टेंटिएशन की संख्या कम करना
हालाँकि टेम्पलेट तात्कालिकता की संख्या को कम करने के लिए कोई सटीक समाधान नहीं है, फिर भी कुछ दिशानिर्देश हैं जो मदद कर सकते हैं:
एक से अधिक टेम्पलेट तर्कों के साथ रिफैक्टर वर्ग
उदाहरण के लिए, यदि आपके पास एक वर्ग है,
template <typename T, typename U>
struct foo { };
और दोनों का T और U10 अलग अलग विकल्प हैं कर सकते हैं, तो आप इस वर्ग के संभावित टेम्पलेट instantiations 100 एक तरह से करने के लिए इस सार के लिए एक अलग वर्ग के लिए कोड के आम हिस्सा है हल करने के लिए बढ़ा दी है। दूसरी विधि वंशानुक्रम व्युत्क्रम (वर्ग पदानुक्रम को उलट) का उपयोग करना है, लेकिन यह सुनिश्चित करें कि इस तकनीक का उपयोग करने से पहले आपके डिजाइन लक्ष्यों से समझौता नहीं किया जाता है।
व्यक्तिगत अनुवाद इकाइयों के लिए रिफलेक्टर नॉन-टेम्पलेटेड कोड
इस तकनीक का उपयोग करके, आप एक बार सामान्य अनुभाग को संकलित कर सकते हैं और बाद में इसे अपने अन्य टीयू (अनुवाद इकाइयों) के साथ जोड़ सकते हैं।
बाहरी टेम्प्लेट इंस्टेंटिएशन का उपयोग करें (C ++ 11 के बाद से)
यदि आप किसी वर्ग की सभी संभावित बारीकियों को जानते हैं तो आप इस तकनीक का उपयोग सभी मामलों को एक अलग अनुवाद इकाई में संकलित करने के लिए कर सकते हैं।
उदाहरण के लिए:
enum class PossibleChoices = {Option1, Option2, Option3}
template <PossibleChoices pc>
struct foo { };
हम जानते हैं कि इस वर्ग में तीन संभावित तात्कालिकताएँ हो सकती हैं:
template class foo<PossibleChoices::Option1>;
template class foo<PossibleChoices::Option2>;
template class foo<PossibleChoices::Option3>;
ऊपर एक अनुवाद इकाई में रखें और अपनी हेडर फ़ाइल में कक्षा की परिभाषा के नीचे बाहरी कीवर्ड का उपयोग करें:
extern template class foo<PossibleChoices::Option1>;
extern template class foo<PossibleChoices::Option2>;
extern template class foo<PossibleChoices::Option3>;
यह तकनीक आपको समय की बचत कर सकती है यदि आप विभिन्न परीक्षणों को एक सामान्य सेट के साथ संकलित कर रहे हैं।
नोट: MPICH2 इस बिंदु पर स्पष्ट तात्कालिकता की उपेक्षा करता है और हमेशा सभी संकलन इकाइयों में तात्कालिक कक्षाओं को संकलित करता है।
एकता बिल्ड का उपयोग करें
एकता के निर्माण के पीछे का पूरा विचार उन सभी .cc फ़ाइलों को शामिल करना है जो आप एक फ़ाइल में उपयोग करते हैं और उस फ़ाइल को केवल एक बार संकलित करते हैं। इस पद्धति का उपयोग करके, आप विभिन्न फ़ाइलों के सामान्य वर्गों को फिर से स्थापित करने से बच सकते हैं और यदि आपकी परियोजना में बहुत सारी सामान्य फ़ाइलें शामिल हैं, तो आप संभवतः डिस्क एक्सेस पर भी बचत करेंगे।
उदाहरण के लिए, मान लें कि आप तीन फ़ाइलें जाने foo1.cc, foo2.cc, foo3.ccऔर वे सभी शामिल हैं tupleसे एसटीएल । आप ऐसा बना सकते हैं foo-all.ccजो दिखता है:
#include "foo1.cc"
#include "foo2.cc"
#include "foo3.cc"
आप इस फ़ाइल को केवल एक बार संकलित करते हैं और संभावित रूप से तीन फ़ाइलों के बीच सामान्य तात्कालिकता को कम करते हैं। आम तौर पर यह अनुमान लगाना कठिन है कि सुधार महत्वपूर्ण हो सकता है या नहीं। लेकिन एक स्पष्ट तथ्य यह है कि आप अपने बिल्ड में समानता खो देंगे (आप एक ही समय में तीन फ़ाइलों को संकलित नहीं कर सकते हैं)।
इसके अलावा, यदि इनमें से कोई भी फ़ाइल बहुत अधिक मेमोरी लेने के लिए होती है, तो संकलन समाप्त होने से पहले आप वास्तव में मेमोरी से बाहर भाग सकते हैं। कुछ संकलक, जैसे GCC पर , यह ICE (आंतरिक कंपाइलर त्रुटि) आपके कंपाइलर को मेमोरी की कमी के कारण हो सकता है। इस तकनीक का उपयोग न करें जब तक कि आप सभी पेशेवरों और विपक्षों को नहीं जानते।
पूर्वनिर्धारित हेडर
Precompiled हेडर (PCH) संकलक द्वारा पहचाने जाने वाले मध्यवर्ती प्रतिनिधित्व के लिए आपकी हेडर फ़ाइलों को संकलित करके संकलन में बहुत समय बचा सकते हैं। पहले से तैयार हेडर फ़ाइलों को बनाने के लिए, आपको केवल अपनी हेडर फ़ाइल को अपने नियमित संकलन कमांड के साथ संकलित करना होगा। उदाहरण के लिए, जीसीसी पर:
$ g++ YOUR_HEADER.hpp
यह उत्पन्न होगा एक YOUR_HEADER.hpp.gch file( .gchएक ही फ़ोल्डर में जीसीसी में PCH फ़ाइलों के लिए विस्तार है)। इसका मतलब यह है कि यदि आप YOUR_HEADER.hppकिसी अन्य फ़ाइल में शामिल करते हैं , तो कंपाइलर आपके YOUR_HEADER.hpp.gchबजाय YOUR_HEADER.hppपहले वाले फ़ोल्डर में उपयोग करेगा ।
इस तकनीक के साथ दो मुद्दे हैं:
- आपको यह सुनिश्चित करना होगा कि पूर्वनिर्धारित की जा रही हेडर फाइलें स्थिर हैं और बदलने वाली नहीं हैं ( आप हमेशा अपना मेकफाइल बदल सकते हैं )
- आप केवल एक पीसीएच प्रति संकलन इकाई (अधिकांश संकलक पर) शामिल कर सकते हैं। इसका मतलब है कि यदि आपके पास एक से अधिक हेडर फ़ाइल प्री-कंपाइल की जानी हैं, तो आपको उन्हें एक फ़ाइल (जैसे
all-my-headers.hpp) में शामिल करना होगा। लेकिन इसका मतलब है कि आपको नई फाइल को सभी जगहों पर शामिल करना होगा। सौभाग्य से, जीसीसी के पास इस समस्या का समाधान है। इसका उपयोग करें -includeऔर इसे नई हेडर फ़ाइल दें। आप इस तकनीक का उपयोग करके अलग-अलग फ़ाइलों को कॉमा कर सकते हैं।
उदाहरण के लिए:
g++ foo.cc -include all-my-headers.hpp
अनाम या अनाम नामस्थान का उपयोग करें
अनाम स्थान (उर्फ अनाम नामस्थान) उत्पन्न बाइनरी आकार को काफी कम कर सकते हैं। हेज़स्पेस आंतरिक लिंकेज का उपयोग करते हैं, जिसका अर्थ है कि उन नामस्थानों में उत्पन्न प्रतीक अन्य टीयू (अनुवाद या संकलन इकाइयों) को दिखाई नहीं देंगे। कंपाइलर आमतौर पर अनाम नामस्थान के लिए अद्वितीय नाम उत्पन्न करते हैं। इसका मतलब है कि अगर आपके पास कोई फ़ाइल foo.hpp है:
namespace {
template <typename T>
struct foo { };
} // Anonymous namespace
using A = foo<int>;
और आप इस फाइल को दो TUs (दो .cc फ़ाइलों में शामिल करें और उन्हें अलग-अलग संकलित करें) में शामिल करें। दो फू टेम्पलेट उदाहरण समान नहीं होंगे। यह वन डेफिनिशन रूल (ODR) का उल्लंघन करता है । उसी कारण से, अनाम नामस्थान का उपयोग करके हेडर फ़ाइलों में हतोत्साहित किया जाता है। .ccअपनी बाइनरी फ़ाइलों में दिखाए गए प्रतीकों से बचने के लिए अपनी फ़ाइलों में उनका उपयोग करने के लिए स्वतंत्र महसूस करें । कुछ मामलों में, .ccफ़ाइल के लिए सभी आंतरिक विवरणों को बदलने से उत्पन्न बाइनरी आकारों में 10% की कमी देखी गई।
दृश्यता विकल्प बदलना
नए संकलक में आप अपने प्रतीकों को डायनामिक शेयर्ड ऑब्जेक्ट्स (DSO) में दृश्यमान या अदृश्य होने के लिए चुन सकते हैं। आदर्श रूप से, दृश्यता बदलने से कंपाइलर प्रदर्शन, लिंक टाइम ऑप्टिमाइज़ेशन (LTO), और उत्पन्न बाइनरी आकार में सुधार हो सकता है। यदि आप GCC में STL हैडर फ़ाइलों को देखते हैं तो आप देख सकते हैं कि इसका व्यापक रूप से उपयोग किया जाता है। दृश्यता विकल्पों को सक्षम करने के लिए, आपको अपना कोड प्रति फ़ंक्शन, प्रति वर्ग, प्रति चर और अधिक महत्वपूर्ण रूप से प्रति संकलक में बदलना होगा।
दृश्यता की सहायता से आप उन प्रतीकों को छिपा सकते हैं जिन्हें आप उन्हें उत्पन्न साझा वस्तुओं से निजी मानते हैं। जीसीसी पर आप डिफ़ॉल्ट रूप से या -visibilityअपने कंपाइलर के विकल्प को छिपाकर प्रतीकों की दृश्यता को नियंत्रित कर सकते हैं । यह कुछ अर्थों में अनाम नामस्थान के समान है, लेकिन अधिक विस्तृत और घुसपैठ तरीके से।
यदि आप प्रति मामले में विज़ुअलाइज़ेशन निर्दिष्ट करना चाहते हैं, तो आपको अपने कार्यों, चर, और कक्षाओं में निम्नलिखित विशेषताएं जोड़ना होंगी:
__attribute__((visibility("default"))) void foo1() { }
__attribute__((visibility("hidden"))) void foo2() { }
__attribute__((visibility("hidden"))) class foo3 { };
void foo4() { }
जीसीसी में डिफ़ॉल्ट दृश्यता डिफ़ॉल्ट (सार्वजनिक) है, जिसका अर्थ है कि यदि आप उपरोक्त को एक साझा पुस्तकालय ( -shared) विधि के रूप में संकलित करते हैं , foo2और foo3अन्य टीयूएस ( foo1और दृश्यमान होगा ) में कक्षा दिखाई नहीं foo4देगी। यदि आप संकलित करते हैं -visibility=hiddenतो केवल foo1दिखाई देगा। foo4छुप भी जाता होगा।
आप जीसीसी विकी पर दृश्यता के बारे में अधिक पढ़ सकते हैं ।