C++
C ++ में अनुकूलन
खोज…
खाली बेस क्लास ऑप्टिमाइज़ेशन
एक ऑब्जेक्ट 1 बाइट से कम पर कब्जा नहीं कर सकता है, क्योंकि तब इस प्रकार के एक सरणी के सदस्यों का पता एक ही होगा। इस प्रकार sizeof(T)>=1
हमेशा धारण करता है। यह भी सच है कि एक व्युत्पन्न वर्ग अपने किसी भी आधार वर्ग से छोटा नहीं हो सकता है। हालांकि, जब आधार वर्ग खाली होता है, तो इसका आकार आवश्यक रूप से व्युत्पन्न वर्ग में नहीं जोड़ा जाता है:
class Base {};
class Derived : public Base
{
public:
int i;
};
इस मामले में, Derived
लिए Base
के लिए एक बाइट आवंटित करने की आवश्यकता नहीं है प्रति वस्तु के अनुसार एक अलग पता होना चाहिए। यदि खाली बेस क्लास ऑप्टिमाइज़ेशन किया जाता है (और कोई पेडिंग की आवश्यकता नहीं है), तो sizeof(Derived) == sizeof(int)
, यानी खाली आधार के लिए कोई अतिरिक्त आवंटन नहीं किया जाता है। यह कई बेस क्लास के साथ भी संभव है (C ++ में, कई बेस एक ही प्रकार के नहीं हो सकते हैं, इसलिए इससे कोई समस्या नहीं होती है)।
ध्यान दें कि यह केवल तभी किया जा सकता है जब Derived
का पहला सदस्य किसी भी आधार वर्ग के प्रकार से भिन्न हो। इसमें कोई भी प्रत्यक्ष या अप्रत्यक्ष आम आधार शामिल है। यदि यह एक ही प्रकार है जिसमें से एक आधार (या वहाँ एक सामान्य आधार) है, तो कम से कम एक बाइट आवंटित करना यह सुनिश्चित करने के लिए आवश्यक है कि एक ही प्रकार की कोई दो अलग-अलग वस्तुओं का एक ही पता न हो।
प्रदर्शन का परिचय
C और C ++ को उच्च प्रदर्शन वाली भाषाओं के रूप में जाना जाता है - मोटे तौर पर कोड अनुकूलन की भारी मात्रा के कारण, उपयोगकर्ता को संरचना की पसंद से प्रदर्शन को निर्दिष्ट करने की अनुमति देता है।
जब अनुकूलन यह प्रासंगिक कोड बेंचमार्क करने के लिए महत्वपूर्ण है और पूरी तरह से समझते हैं कि कोड का उपयोग कैसे किया जाएगा।
सामान्य अनुकूलन गलतियों में शामिल हैं:
- समय से पहले अनुकूलन: जटिल कोड अनुकूलन, समय और प्रयास को बर्बाद करने के बाद बदतर प्रदर्शन कर सकता है। पहली प्राथमिकता अनुकूलित कोड के बजाय सही और बनाए रखने योग्य कोड लिखने की होनी चाहिए।
- गलत उपयोग मामले के लिए अनुकूलन: 1% के लिए ओवरहेड जोड़ना अन्य 99% के लिए मंदी के लायक नहीं हो सकता है
- माइक्रो-ऑप्टिमाइज़ेशन: कंपाइलर बहुत कुशलता से करते हैं और माइक्रो-ऑप्टिमाइज़ेशन कंपाइलर्स की क्षमता को और भी बेहतर बना सकते हैं ताकि कोड को ऑप्टिमाइज़ किया जा सके
विशिष्ट अनुकूलन लक्ष्य हैं:
- कम काम करना
- अधिक कुशल एल्गोरिदम / संरचनाओं का उपयोग करने के लिए
- हार्डवेयर का बेहतर उपयोग करने के लिए
अनुकूलित कोड के नकारात्मक दुष्प्रभाव हो सकते हैं, जिनमें शामिल हैं:
- उच्च स्मृति उपयोग
- जटिल कोड-पढ़ने या बनाए रखने में मुश्किल
- समझौता एपीआई और कोड डिजाइन
कम कोड निष्पादित करके अनुकूलन
अनुकूलन के लिए सबसे सरल दृष्टिकोण कम कोड को निष्पादित करके है। यह दृष्टिकोण आमतौर पर कोड की समय जटिलता को बदलने के बिना एक निश्चित गति-अप देता है।
भले ही यह दृष्टिकोण आपको एक स्पष्ट गति प्रदान करता है, लेकिन यह केवल ध्यान देने योग्य सुधार देगा जब कोड को बहुत कुछ कहा जाता है।
बेकार कोड निकालना
void func(const A *a); // Some random function
// useless memory allocation + deallocation for the instance
auto a1 = std::make_unique<A>();
func(a1.get());
// making use of a stack object prevents
auto a2 = A{};
func(&a2);
सी ++ 14 से, संकलक को आवंटन और मिलान डीललैशन को हटाने के लिए इस कोड को अनुकूलित करने की अनुमति है।
केवल एक बार कोड करना
std::map<std::string, std::unique_ptr<A>> lookup;
// Slow insertion/lookup
// Within this function, we will traverse twice through the map lookup an element
// and even a thirth time when it wasn't in
const A *lazyLookupSlow(const std::string &key) {
if (lookup.find(key) != lookup.cend())
lookup.emplace_back(key, std::make_unique<A>());
return lookup[key].get();
}
// Within this function, we will have the same noticeable effect as the slow variant while going at double speed as we only traverse once through the code
const A *lazyLookupSlow(const std::string &key) {
auto &value = lookup[key];
if (!value)
value = std::make_unique<A>();
return value.get();
}
इस अनुकूलन के लिए एक समान दृष्टिकोण का उपयोग unique
स्थिर संस्करण को लागू करने के लिए किया जा सकता है
std::vector<std::string> stableUnique(const std::vector<std::string> &v) {
std::vector<std::string> result;
std::set<std::string> checkUnique;
for (const auto &s : v) {
// As insert returns if the insertion was successful, we can deduce if the element was already in or not
// This prevents an insertion, which will traverse through the map for every unique element
// As a result we can almost gain 50% if v would not contain any duplicates
if (checkUnique.insert(s).second)
result.push_back(s);
}
return result;
}
बेकार वसूली और नकल / स्थानांतरण को रोकना
पिछले उदाहरण में, हमने पहले से ही std :: set में लुकअप को रोक दिया है, हालाँकि std::vector
अभी भी एक बढ़ते एल्गोरिथ्म है, जिसमें इसके भंडारण को फिर से करना होगा। यह सही आकार के लिए पहले जलाकर रोका जा सकता है।
std::vector<std::string> stableUnique(const std::vector<std::string> &v) {
std::vector<std::string> result;
// By reserving 'result', we can ensure that no copying or moving will be done in the vector
// as it will have capacity for the maximum number of elements we will be inserting
// If we make the assumption that no allocation occurs for size zero
// and allocating a large block of memory takes the same time as a small block of memory
// this will never slow down the program
// Side note: Compilers can even predict this and remove the checks the growing from the generated code
result.reserve(v.size());
std::set<std::string> checkUnique;
for (const auto &s : v) {
// See example above
if (checkUnique.insert(s).second)
result.push_back(s);
}
return result;
}
कुशल कंटेनरों का उपयोग करना
सही समय पर सही डेटा संरचनाओं का उपयोग करके अनुकूलन कोड की समय-जटिलता को बदल सकता है।
// This variant of stableUnique contains a complexity of N log(N)
// N > number of elements in v
// log(N) > insert complexity of std::set
std::vector<std::string> stableUnique(const std::vector<std::string> &v) {
std::vector<std::string> result;
std::set<std::string> checkUnique;
for (const auto &s : v) {
// See Optimizing by executing less code
if (checkUnique.insert(s).second)
result.push_back(s);
}
return result;
}
एक कंटेनर का उपयोग करके जो अपने तत्वों (पेड़ के बजाय हैश कंटेनर) के भंडारण के लिए एक अलग कार्यान्वयन का उपयोग करता है, हम अपने कार्यान्वयन को जटिलता एन में बदल सकते हैं। साइड इफेक्ट के रूप में, हम std :: string के लिए तुलना ऑपरेटर को कम कहेंगे, क्योंकि यह केवल तब ही कॉल किया जाना चाहिए जब सम्मिलित स्ट्रिंग उसी बाल्टी में समाप्त होनी चाहिए।
// This variant of stableUnique contains a complexity of N
// N > number of elements in v
// 1 > insert complexity of std::unordered_set
std::vector<std::string> stableUnique(const std::vector<std::string> &v) {
std::vector<std::string> result;
std::unordered_set<std::string> checkUnique;
for (const auto &s : v) {
// See Optimizing by executing less code
if (checkUnique.insert(s).second)
result.push_back(s);
}
return result;
}
छोटी वस्तु अनुकूलन
स्मॉल ऑब्जेक्ट ऑप्टिमाइज़ेशन एक ऐसी तकनीक है, जिसका उपयोग निम्न स्तर की डेटा संरचनाओं के लिए किया जाता है, उदाहरण के लिए std::string
(कभी-कभी शॉर्ट / स्मॉल स्ट्रिंग ऑप्टिमाइज़ेशन के रूप में संदर्भित)। यह सामग्री को आरक्षित स्थान के भीतर फिट करने के लिए पर्याप्त छोटा होने के बजाय कुछ आवंटित मेमोरी के बजाय बफर के रूप में स्टैक स्पेस का उपयोग करने के लिए है।
अतिरिक्त मेमोरी ओवरहेड और अतिरिक्त गणनाओं को जोड़कर, यह एक महंगे ढेर आवंटन को रोकने की कोशिश करता है। इस तकनीक के लाभ उपयोग पर निर्भर हैं और गलत तरीके से उपयोग किए जाने पर प्रदर्शन को नुकसान पहुंचा सकते हैं।
उदाहरण
इस अनुकूलन के साथ एक स्ट्रिंग को लागू करने का एक बहुत ही अच्छा तरीका निम्न होगा:
#include <cstring>
class string final
{
constexpr static auto SMALL_BUFFER_SIZE = 16;
bool _isAllocated{false}; ///< Remember if we allocated memory
char *_buffer{nullptr}; ///< Pointer to the buffer we are using
char _smallBuffer[SMALL_BUFFER_SIZE]= {'\0'}; ///< Stack space used for SMALL OBJECT OPTIMIZATION
public:
~string()
{
if (_isAllocated)
delete [] _buffer;
}
explicit string(const char *cStyleString)
{
auto stringSize = std::strlen(cStyleString);
_isAllocated = (stringSize > SMALL_BUFFER_SIZE);
if (_isAllocated)
_buffer = new char[stringSize];
else
_buffer = &_smallBuffer[0];
std::strcpy(_buffer, &cStyleString[0]);
}
string(string &&rhs)
: _isAllocated(rhs._isAllocated)
, _buffer(rhs._buffer)
, _smallBuffer(rhs._smallBuffer) //< Not needed if allocated
{
if (_isAllocated)
{
// Prevent double deletion of the memory
rhs._buffer = nullptr;
}
else
{
// Copy over data
std::strcpy(_smallBuffer, rhs._smallBuffer);
_buffer = &_smallBuffer[0];
}
}
// Other methods, including other constructors, copy constructor,
// assignment operators have been omitted for readability
};
जैसा कि आप ऊपर दिए गए कोड में देख सकते हैं, कुछ new
और delete
ऑपरेशन को रोकने के लिए कुछ अतिरिक्त जटिलता जोड़ी गई है। इसके शीर्ष पर, वर्ग के पास एक बड़ा मेमोरी फ़ुटप्रिंट होता है जिसका उपयोग एक दो मामलों को छोड़कर नहीं किया जा सकता है।
अक्सर यह एकल उदाहरण (इंटेल 64 बिट: 8 बाइट द्वारा आकार को कम कर सकता है) के आकार को कम करने के लिए बिट हेरफेर के साथ सूचक _buffer
भीतर, बूल मूल्य _isAllocated
को एन्कोड करने की कोशिश की जाती है। एक अनुकूलन जो केवल तभी संभव है जब इसके ज्ञात को प्लेटफ़ॉर्म के संरेखण नियम क्या हों।
कब इस्तेमाल करें?
चूंकि यह अनुकूलन बहुत अधिक जटिलता जोड़ता है, इसलिए प्रत्येक एकल वर्ग पर इस अनुकूलन का उपयोग करने की अनुशंसा नहीं की जाती है। यह अक्सर आम तौर पर उपयोग किए जाने वाले, निम्न-स्तरीय डेटा संरचनाओं में सामना किया जाएगा। सामान्य C ++ 11 standard library
कार्यान्वयन में, किसी को std::basic_string<>
में usages मिल सकता है std::basic_string<>
और std::function<>
।
चूंकि यह अनुकूलन केवल मेमोरी आवंटन को रोकता है जब संग्रहीत डेटा बफर से छोटा होता है, तो यह केवल लाभ देगा यदि वर्ग को अक्सर छोटे डेटा के साथ उपयोग किया जाता है।
इस अनुकूलन का एक अंतिम दोष यह है कि बफर को स्थानांतरित करते समय अतिरिक्त प्रयास की आवश्यकता होती है, जब बफर का उपयोग नहीं किया जाएगा, तो मूव-ऑपरेशन को अधिक महंगा बना देगा। यह विशेष रूप से सच है जब बफर में गैर-पीओडी प्रकार होता है।