c++ आधुनिक सी++ कंपाइलर्स पर प्रभावी अनुकूलन रणनीतियों



जावा प्रोग्रामिंग भाषा (13)

कैसे कैश-परिचित कंपेलरों को लगता है? उदाहरण के लिए, क्या यह घोंसला वाले नेस्टेड लूपों को देखने में लायक है?

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

https://ffff65535.com

मैं वैज्ञानिक कोड पर काम कर रहा हूं जो बहुत ही महत्वपूर्ण-महत्वपूर्ण है। कोड का प्रारंभिक संस्करण लिखा और परीक्षण किया गया है, और अब, प्रोफाइलर के साथ, अब गर्म स्थानों से शेविंग चक्र शुरू करने का समय है।

यह अच्छी तरह से ज्ञात है कि कुछ ऑप्टिमाइज़ेशन, जैसे लूप अनोलिंग, हाथ से दखल देने वाले प्रोग्रामर की तुलना में कंपाइलर द्वारा इन दिनों अधिक प्रभावी ढंग से संभाले जाते हैं। कौन सी तकनीक अभी भी सार्थक है? जाहिर है, मैं एक प्रोफाइलर के माध्यम से कोशिश की हर चीज चलाऊंगा, लेकिन अगर पारंपरिक ज्ञान है कि क्या काम करता है और क्या नहीं करता है, तो यह मुझे महत्वपूर्ण समय बचाएगा।

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

यहां कुछ ठोस विचार दिए गए हैं जिन पर मैं विचार कर रहा हूं:

  • हाथ से लुढ़काए गए एसटीएल कंटेनर / एल्गोरिदम को बदलने के लिए कोई फायदा है? विशेष रूप से, मेरे कार्यक्रम में एक बहुत बड़ी प्राथमिकता कतार शामिल है (वर्तमान में एक std::priority_queue ) जिसका मैनिपुलेशन बहुत अधिक समय ले रहा है। क्या यह कुछ देखने लायक है, या एसटीएल कार्यान्वयन पहले से ही सबसे तेज़ संभव है?
  • इसी तरह की रेखाओं के साथ, std::vector लिए जिनके आवश्यक आकार अज्ञात हैं लेकिन एक उचित रूप से छोटे ऊपरी बाउंड हैं, क्या यह स्थिर रूप से आवंटित सरणी के साथ उन्हें बदलने के लिए लाभदायक है?
  • मैंने पाया है कि गतिशील स्मृति आवंटन अक्सर एक गंभीर बाधा है, और इससे इसे समाप्त करने से महत्वपूर्ण गति-अप हो सकता है। नतीजतन, मैं सूचकांक बनाम बड़े पैमाने पर अस्थायी डेटा संरचनाओं को लौटने के प्रदर्शन व्यापार में दिलचस्प हूं, जिसके परिणामस्वरूप परिणाम बनाकर पॉइंटर बनाम लौट रहा है। क्या विश्वसनीय तरीके से यह निर्धारित करने का कोई तरीका है कि संकलक किसी दिए गए विधि के लिए आरवीओ का उपयोग करेगा या नहीं (मान लीजिए कि कॉलर को परिणाम को संशोधित करने की आवश्यकता नहीं है)?
  • कैसे कैश-परिचित कंपेलरों को लगता है? उदाहरण के लिए, क्या यह घोंसला वाले नेस्टेड लूपों को देखने में लायक है?
  • कार्यक्रम की वैज्ञानिक प्रकृति को देखते हुए, फ्लोटिंग-पॉइंट नंबर हर जगह उपयोग किए जाते हैं। मेरे कोड में एक महत्वपूर्ण बाधा फ्लोटिंग पॉइंट से पूर्णांक तक रूपांतरण होती थी: संकलक वर्तमान राउंडिंग मोड को सहेजने के लिए कोड उत्सर्जित करेगा, इसे बदल देगा, रूपांतरण करेगा, फिर पुराने राउंडिंग मोड को पुनर्स्थापित करेगा --- भले ही प्रोग्राम में कुछ भी नहीं कभी राउंडिंग मोड बदल दिया! इस व्यवहार को अक्षम करने से मेरे कोड को काफी हद तक बढ़ा दिया गया। क्या कोई समान फ़्लोटिंग-पॉइंट-संबंधित गॉथस है जिसके बारे में मुझे अवगत होना चाहिए?
  • संकलित और अलग से जुड़े हुए C ++ का एक परिणाम यह है कि संकलक ऐसा करने में असमर्थ है जो बहुत सरल अनुकूलन प्रतीत होता है, जैसे लूप की समाप्ति शर्तों से स्ट्रेल () जैसे चाल विधि कॉल। क्या इस तरह कोई अनुकूलन है जिसे मुझे देखना चाहिए क्योंकि उन्हें कंपाइलर द्वारा नहीं किया जा सकता है और हाथ से किया जाना चाहिए?
  • फ्लिप पक्ष पर, क्या ऐसी कोई तकनीक है जो मुझे टालना चाहिए क्योंकि वे स्वचालित रूप से कोड को अनुकूलित करने के लिए कंपाइलर की क्षमता में हस्तक्षेप करने की संभावना रखते हैं?

आखिरकार, कद्दू में कुछ प्रकार के उत्तरों को निपटाएं:

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

हाथ से लुढ़काए गए एसटीएल कंटेनर / एल्गोरिदम को बदलने के लिए कोई फायदा है? विशेष रूप से, मेरे कार्यक्रम में एक बहुत बड़ी प्राथमिकता कतार शामिल है (वर्तमान में एक std :: priority_queue) जिसका मैनिपुलेशन बहुत अधिक समय ले रहा है। क्या यह कुछ देखने लायक है, या एसटीएल कार्यान्वयन पहले से ही सबसे तेज़ संभव है?

एसटीएल आमतौर पर सबसे तेज़, सामान्य मामला है। यदि आपके पास एक बहुत ही विशिष्ट मामला है, तो आप एक हाथ से लुढ़कने वाला एक स्पीड-अप देख सकते हैं। उदाहरण के लिए, std :: sort (सामान्यतः quicksort) सबसे तेज़ सामान्य प्रकार है, लेकिन यदि आप पहले से जानते हैं कि आपके तत्व वस्तुतः पहले ही ऑर्डर किए गए हैं, तो सम्मिलन क्रम बेहतर विकल्प हो सकता है।

समान रेखाओं के साथ, std :: vectors के लिए जिनके आवश्यक आकार अज्ञात हैं लेकिन एक उचित रूप से छोटे ऊपरी बाउंड हैं, क्या यह स्थिर रूप से आवंटित सरणी के साथ उन्हें बदलने के लिए लाभदायक है?

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

I've found that dynamic memory allocation is often a severe bottleneck, and that eliminating it can lead to significant speedups. As a consequence I'm interesting in the performance tradeoffs of returning large temporary data structures by value vs. returning by pointer vs. passing the result in by reference. Is there a way to reliably determine whether or not the compiler will use RVO for a given method (assuming the caller doesn't need to modify the result, of course)?

I personally normally pass the result in by reference in this scenario. It allows for a lot more re-use. Passing large data structures by value and hoping that the compiler uses RVO is not a good idea when you can just manually use RVO yourself.

How cache-aware do compilers tend to be? For example, is it worth looking into reordering nested loops?

I found that they weren't particularly cache-aware. The issue is that the compiler doesn't understand your program and can't predict the vast majority of it's state, especially if you depend heavily on heap. If you have a profiler that ships with your compiler, for example Visual Studio's Profile Guided Optimization, then this can produce excellent speedups.

Given the scientific nature of the program, floating-point numbers are used everywhere. A significant bottleneck in my code used to be conversions from floating point to integers: the compiler would emit code to save the current rounding mode, change it, perform the conversion, then restore the old rounding mode --- even though nothing in the program ever changed the rounding mode! Disabling this behavior significantly sped up my code. Are there any similar floating-point-related gotchas I should be aware of?

There are different floating-point models - Visual Studio gives an fp:fast compiler setting. As for the exact effects of doing such, I can't be certain. However, you could try altering the floating point precision or other settings in your compiler and checking the result.

One consequence of C++ being compiled and linked separately is that the compiler is unable to do what would seem to be very simple optimizations, such as move method calls like strlen() out of the termination conditions of loop. Are there any optimization like this one that I should look out for because they can't be done by the compiler and must be done by hand?

I've never come across such a scenario. However, if you're genuinely concerned about such, then the option remains to do it manually. One of the things that you could try is calling a function on a const reference, suggesting to the compiler that the value won't change.

One of the other things that I want to point out is the use of non-standard extensions to the compiler, for example provided by Visual Studio is __assume. http://msdn.microsoft.com/en-us/library/1b3fsfxw(VS.80).aspx

There's also multithread, which I would expect you've gone down that road. You could try some specific opts, like another answer suggested SSE.

Edit: I realized that a lot of the suggestions I posted referenced Visual Studio directly. That's true, but, GCC almost certainly provides alternatives to the majority of them. I just have personal experience with VS most.


हाथ से लुढ़काए गए एसटीएल कंटेनर / एल्गोरिदम को बदलने के लिए कोई फायदा है?

आम तौर पर, जब तक कि आप खराब कार्यान्वयन के साथ काम नहीं कर रहे हैं। मैं सिर्फ एसटीएल कंटेनर या एल्गोरिदम को प्रतिस्थापित नहीं करता क्योंकि आपको लगता है कि आप कड़े कोड लिख सकते हैं। मैं केवल तभी ऐसा करूंगा जब एसटीएल संस्करण आपकी समस्या के मुकाबले ज्यादा सामान्य हो। यदि आप एक सरल संस्करण लिख सकते हैं जो आपको केवल वही करता है, तो वहां कुछ गति हो सकती है।

एक अपवाद मैंने देखा है कि एक कॉपी-ऑन-राइट std :: स्ट्रिंग को उस स्थान से प्रतिस्थापित करना है जिसके लिए थ्रेड सिंक्रनाइज़ेशन की आवश्यकता नहीं है।

std :: vectors के लिए जिनकी आवश्यक आकार अज्ञात हैं लेकिन एक उचित रूप से छोटी ऊपरी सीमा है, क्या यह स्थिर रूप से आवंटित सरणी के साथ उन्हें बदलने के लिए लाभदायक है?

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

संदर्भ बनाम परिणाम बनाकर पॉइंटर बनाम द्वारा लौटकर मूल्य बनाम बड़े अस्थायी डेटा संरचनाओं को लौटने का प्रदर्शन व्यापार।

कंटेनर के साथ काम करते समय, मैं इनपुट और आउटपुट इटरेटर के लिए इटरेटर पास करता हूं, जो अभी भी बहुत सामान्य है।

कैसे कैश-परिचित कंपेलरों को लगता है? उदाहरण के लिए, क्या यह घोंसला वाले नेस्टेड लूपों को देखने में लायक है?

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

मेरे कोड में एक महत्वपूर्ण बाधा फ्लोटिंग पॉइंट से पूर्णांक तक रूपांतरण होती थी

हाँ। मैंने हाल ही में एक ही समस्या की खोज की।

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

कुछ कंपाइलर्स इससे निपट सकते हैं। विज़ुअल सी ++ में "लिंक-टाइम कोड जनरेशन" विकल्प है जो प्रभावी अनुकूलन करने के लिए कंपाइलर को फिर से आमंत्रित करता है। और, strlen जैसे कार्यों के मामले में, कई कंपाइलर्स एक आंतरिक कार्य के रूप में पहचानेंगे।

क्या इस तरह कोई अनुकूलन है जिसे मुझे देखना चाहिए क्योंकि उन्हें कंपाइलर द्वारा नहीं किया जा सकता है और हाथ से किया जाना चाहिए? फ्लिप पक्ष पर, क्या ऐसी कोई तकनीक है जो मुझे टालना चाहिए क्योंकि वे स्वचालित रूप से कोड को अनुकूलित करने के लिए कंपाइलर की क्षमता में हस्तक्षेप करने की संभावना रखते हैं?

जब आप इस निम्न स्तर पर अनुकूलित कर रहे हैं, अंगूठे के कुछ विश्वसनीय नियम हैं। कंपाइलर्स अलग-अलग होंगे। अपने वर्तमान समाधान को मापें, और यह तय करें कि यह बहुत धीमा है या नहीं। यदि ऐसा है, तो एक परिकल्पना के साथ आओ (उदाहरण के लिए, "अगर मैं एक लुक-अप टेबल के साथ आंतरिक if-statement को प्रतिस्थापित करता हूं तो क्या होगा?")। यह मदद कर सकता है ("असफल शाखा भविष्यवाणियों के कारण स्टालों को समाप्त करता है") या यह चोट पहुंचा सकता है ("लुक-अप एक्सेस पैटर्न कैश समेकन को नुकसान पहुंचाता है")। प्रयोग और माप वृद्धिशील।

मैं अक्सर सीधा कार्यान्वयन क्लोन कर #ifdef HAND_OPTIMIZED और संदर्भ संस्करण और tweaked संस्करण के बीच स्विच करने के लिए #else #ifdef HAND_OPTIMIZED / #else / #endif का उपयोग #else । यह बाद में कोड रखरखाव और सत्यापन के लिए उपयोगी है। मैं प्रत्येक सफल प्रयोग को नियंत्रण बदलने के लिए प्रतिबद्ध करता हूं, और चेंजलिस्ट नंबर, रन टाइम और ऑप्टिमाइज़ेशन में प्रत्येक चरण के लिए स्पष्टीकरण के साथ एक लॉग (स्प्रेडशीट) रखता हूं। जैसा कि मैं कोड के व्यवहार के तरीके के बारे में और जानता हूं, लॉग बैक अप करना और दूसरी दिशा में शाखा बनाना आसान बनाता है।

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


One consequence of C++ being compiled and linked separately is that the compiler is unable to do what would seem to be very simple optimizations, such as move method calls like strlen() out of the termination conditions of loop. Are there any optimization like this one that I should look out for because they can't be done by the compiler and must be done by hand?

On some compilers this is incorrect. The compiler has perfect knowledge of all code across all translation units (including static libraries) and can optimize the code the same way it would do if it were in a single translation unit. A few ones that support this feature come to my mind:

  • Microsoft Visual C++ compilers
  • Intel C++ Compiler
  • LLVC-GCC
  • GCC (I think, not sure)

अगर मैं इस पर काम कर रहा था, तो मैं एक अंत-चरण की अपेक्षा करता हूं जहां कैश इलाके और वेक्टर परिचालन जैसी चीजें खेलेंगी।

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

इस तरह की प्रक्रिया का एक उदाहरण यहां दिया गया है।

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

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


इलाके के लिए पुनर्गठन कोड के बारे में कुछ जानकारी के लिए ऑब्जेक्ट ओरिएंटेड प्रोग्रामिंग स्लाइड के उत्कृष्ट नुकसान पर नज़र डालें। मेरे अनुभव में बेहतर इलाका होने के कारण लगभग हमेशा सबसे बड़ी जीत होती है।

सामान्य प्रक्रिया:

  • डिस्सेप्लर को अपने डीबगर में देखना सीखें, या यदि संभव हो तो अपनी बिल्ड सिस्टम इंटरमीडिएट असेंबली फाइलें (.s) उत्पन्न करती है। परिवर्तनों पर नजर रखें या उन चीज़ों के लिए जो गंभीर दिखते हैं - यहां तक ​​कि किसी दिए गए निर्देश सेट आर्किटेक्चर से परिचित होने के बावजूद, आपको कुछ चीजों को स्पष्ट रूप से देखने में सक्षम होना चाहिए! (मैं कभी-कभी .cpp / .c परिवर्तनों के साथ .s फ़ाइलों की एक श्रृंखला में जांच करता हूं, बस मेरे एससीएम से प्यारे टूल्स का लाभ उठाने के लिए कोड और समय के साथ इसी तरह के परिवर्तन को देखने के लिए।)
  • एक प्रोफाइलर प्राप्त करें जो आपके सीपीयू के प्रदर्शन काउंटर देख सकता है, या कम से कम कैश मिस पर अनुमान लगा सकता है। (एएमडी कोड विश्लेषक, कैशग्रींड, vTune, आदि)

कुछ अन्य विशिष्ट चीजें:

  • सख्त एलियासिंग को समझें। एक बार ऐसा करने के बाद, यदि आपके कंपाइलर के पास है तो restrict उपयोग करें। (यहां भी विवाद की जांच करें!)
  • अपने प्रोसेसर और कंपाइलर पर विभिन्न फ़्लोटिंग पॉइंट मोड देखें । यदि आपको denormalized रेंज की आवश्यकता नहीं है, तो इसके बिना एक मोड चुनने से बेहतर प्रदर्शन हो सकता है। (ऐसा लगता है जैसे आप राउंडिंग मोड की अपनी चर्चा के आधार पर इस क्षेत्र में कुछ चीजें पहले ही कर चुके हैं।)
  • निश्चित रूप से सभी ब्राउज़रों से बचें: जब आप कर सकते हैं std::vector पर कॉल reserve , या संकलन-समय पर आकार को जानते समय std::array उपयोग करें
  • इलाके को बढ़ाने और आवंटन / मुक्त ओवरहेड को कम करने के लिए मेमोरी पूल का उपयोग करें ; कैशलाइन संरेखण सुनिश्चित करने और पिंग-पोंगिंग को रोकने के लिए भी।
  • फ्रेम आवंटकों का उपयोग करें यदि आप अनुमानित पैटर्न में चीजों को आवंटित कर रहे हैं, और एक ही समय में सबकुछ खराब कर सकते हैं।
  • इनवेरिएंट से अवगत रहें । आप जो कुछ जानते हैं वह परिवर्तनीय है, यह कंपाइलर के लिए नहीं हो सकता है, उदाहरण के लिए एक लूप में एक स्ट्रक्चर या क्लास सदस्य का उपयोग। मुझे सही आदत में गिरने का सबसे आसान तरीका यहां सबकुछ नाम देना है, और लूप के बाहर चीजों को नाम देना पसंद करते हैं। जैसे const int threshold = m_currentThreshold; या शायद Thing * const pThing = pStructHoldingThing->pThing; सौभाग्य से आप आम तौर पर ऐसी चीजें देख सकते हैं जिन्हें डिस्सेप्लिब्स व्यू में इस उपचार की आवश्यकता है। यह बाद में डिबगिंग के साथ भी मदद करता है (घड़ी / स्थानीय खिड़की डीबग बिल्ड में बहुत अच्छी तरह से व्यवहार करता है)!
  • यदि संभव हो तो लूप में लिखने से बचें - पहले जमा करें, फिर लिखें, या बैच को कुछ लिखते हैं। निश्चित रूप से वाईएमएमवी।

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


एसटीएल कंटेनर के बारे में।

यहां अधिकांश लोग दावा करते हैं कि एसटीएल कंटेनर एल्गोरिदम के सबसे तेज़ संभव कार्यान्वयन में से एक प्रदान करता है। और मैं इसके विपरीत कहता हूं: सबसे वास्तविक दुनिया परिदृश्यों के लिए एसटीएल कंटेनर के रूप में लिया गया है-वास्तव में विनाशकारी प्रदर्शन पैदा करता है

लोग एसटीएल में इस्तेमाल किए गए एल्गोरिदम की जटिलता के बारे में बहस करते हैं। यहां एसटीएल अच्छा है: map लिए list / queue , वेक्टर (अमूर्त), और ओ (लॉग (एन)) के लिए ओ (1)। लेकिन यह एक सामान्य आवेदन के प्रदर्शन के असली बाधा नहीं है! कई अनुप्रयोगों के लिए असली बाधा ढेर परिचालन है ( malloc / free , new / delete , आदि)।

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

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

दूसरी तरफ अन्य कंटेनर हैं जो अलग-अलग डिजाइन किए गए हैं। एक बार जब मैंने अपनी जरूरतों के लिए ऐसे कंटेनरों को डिज़ाइन और लिखा है:

http://www.codeproject.com/KB/recipes/Containers.aspx

और यह मेरे प्रदर्शन के दृष्टिकोण से बेहतर होने के लिए साबित हुआ, न केवल।

लेकिन हाल ही में मैंने पाया है कि मैं अकेला नहीं हूं जिसने इस बारे में सोचा था। boost::intrusive करने वाला कंटेनर लाइब्रेरी है जो कि मैंने जो किया उसके समान तरीके से लागू किया गया है।

मेरा सुझाव है कि आप इसे आजमाएं (यदि आपने पहले से नहीं किया है)


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

std::vector बदलने के संबंध में: ड्रॉप-इन प्रतिस्थापन का उपयोग करें (उदाहरण के लिए, यह एक ) और बस इसे आज़माएं।


हाथ से लुढ़काए गए एसटीएल कंटेनर / एल्गोरिदम को बदलने के लिए कोई फायदा है?
मैं केवल इसे अंतिम विकल्प के रूप में मानता हूं। एसटीएल कंटेनर और एल्गोरिदम का पूरी तरह से परीक्षण किया गया है। विकास के समय के मामले में नए बनाना महंगा है।

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

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

कैसे कैश-परिचित कंपेलरों को लगता है? उदाहरण के लिए, क्या यह घोंसला वाले नेस्टेड लूपों को देखने में लायक है?
आधुनिक कंपाइलर निर्देश कैश (पाइपलाइन) से बहुत अवगत हैं और उन्हें पुनः लोड होने से रोकने की कोशिश करते हैं। आप कोड को लिखकर हमेशा अपने कंपाइलर की सहायता कर सकते हैं जो कम शाखाओं का उपयोग करता है ( if switch , लूप संरचनाएं और फ़ंक्शन कॉल )।

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

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

इनका उपयोग करने से पहले कंटेनरों में जगह को आरक्षित करने के बारे में ऊपर दिए गए अनुभाग को भी देखें।

कुछ प्रोसेसर फ्लोटिंग पॉइंट नंबरों को या तो तेज़ या पूर्णांक के रूप में तेज़ लोड और स्टोर कर सकते हैं। ऑप्टिमाइज़ करने से पहले इसे प्रोफाइल डेटा एकत्र करने की आवश्यकता होगी। हालांकि, अगर आपको पता है कि न्यूनतम रिज़ॉल्यूशन है, तो आप पूर्णांक का उपयोग कर सकते हैं और उस न्यूनतम रिज़ॉल्यूशन पर अपना आधार बदल सकते हैं। उदाहरण के लिए, यूएस पैसे से निपटने पर, पूर्णांक का उपयोग 1/100 या 1/1000 डॉलर के प्रतिनिधित्व के लिए किया जा सकता है।

संकलित और अलग से जुड़े हुए C ++ का एक परिणाम यह है कि संकलक ऐसा करने में असमर्थ है जो बहुत सरल अनुकूलन प्रतीत होता है, जैसे लूप की समाप्ति शर्तों से स्ट्रेल () जैसे चाल विधि कॉल। क्या इस तरह कोई अनुकूलन है जिसे मुझे देखना चाहिए क्योंकि उन्हें कंपाइलर द्वारा नहीं किया जा सकता है और हाथ से किया जाना चाहिए?
यह एक गलत धारणा है। कंपाइलर फ़ंक्शन के हस्ताक्षर के आधार पर अनुकूलित कर सकते हैं, खासकर यदि पैरामीटर सही ढंग से const उपयोग करते हैं। मैं हमेशा लूप के बाहर निरंतर सामान को स्थानांतरित करके कंपाइलर की सहायता करना चाहता हूं। ऊपरी सीमा मान के लिए, जैसे स्ट्रिंग लम्बाई, लूप से पहले इसे एक const वैरिएबल को असाइन करें। const संशोधक अनुकूलक की सहायता करेगा।

लूप में हमेशा गिनती अनुकूलन होता है। कई प्रोसेसर के लिए, शून्य पर बराबर रजिस्टर पर एक कूद तुलना की तुलना में अधिक कुशल है और यदि कम से कम कूदता है

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

अनुकूलन विचार और अवधारणाओं

1. कंप्यूटर क्रमिक निर्देशों को निष्पादित करना पसंद करते हैं।
शाखा उन्हें परेशान करता है। कुछ आधुनिक प्रोसेसर में छोटे लूप के लिए कोड रखने के लिए पर्याप्त निर्देश कैश होता है। संदेह में, शाखाओं का कारण नहीं है।

2. आवश्यकताएं हटाएं
कम कोड, अधिक प्रदर्शन।

3. कोड से पहले डिज़ाइन अनुकूलित करें कई बार डिजाइन के कार्यान्वयन को बदलकर डिज़ाइन को बदलकर अधिक प्रदर्शन प्राप्त किया जा सकता है। कम डिजाइन कम कोड को बढ़ावा देता है, और अधिक प्रदर्शन उत्पन्न करता है।

4. डेटा संगठन पर विचार करें डेटा अनुकूलित करें।
अक्सर इस्तेमाल किए गए फ़ील्ड को substructures में व्यवस्थित करें। डेटा कैश लाइन में फिट करने के लिए डेटा आकार सेट करें। डेटा संरचनाओं से निरंतर डेटा निकालें।
यथासंभव const विनिर्देशक का प्रयोग करें।

5. पृष्ठ स्वैपिंग पर विचार करें ऑपरेटिंग सिस्टम आपके प्रोग्राम या कार्य को दूसरे के लिए स्वैप कर देंगे। हार्ड ड्राइव पर अक्सर 'स्वैप फ़ाइल' में। कोड को उन हिस्सों में तोड़ना जिनमें भारी निष्पादित कोड और कम निष्पादित कोड शामिल है, ओएस की सहायता करेंगे। इसके अलावा, कड़े इकाइयों में भारी इस्तेमाल कोड को जमा करें। विचार हार्ड ड्राइव से कोड के स्वैपिंग को कम करना है (जैसे "दूर" फ़ंक्शंस प्राप्त करना)। अगर कोड को स्वैप किया जाना चाहिए, तो यह एक इकाई के रूप में होना चाहिए।

6. I / O अनुकूलन पर विचार करें (फ़ाइल I / O भी शामिल है)।
अधिकांश I / O डेटा के कई छोटे हिस्सों में डेटा के कम बड़े हिस्से पसंद करते हैं। हार्ड ड्राइव कताई रखने के लिए पसंद है। छोटे पैकेट की तुलना में बड़े डेटा पैकेट में कम ओवरहेड होता है।
डेटा को बफर में प्रारूपित करें और फिर बफर लिखें।

7. प्रतियोगिता को खत्म करें
प्रोसेसर के लिए आपके आवेदन के खिलाफ प्रतिस्पर्धा कर रहे किसी भी कार्यक्रम और कार्यों से छुटकारा पाएं। वायरस स्कैनिंग और संगीत बजाने जैसे कार्य। यहां तक ​​कि I / O ड्राइवर भी कार्रवाई का एक टुकड़ा चाहते हैं (यही कारण है कि आप संख्या या I / O लेनदेन को कम करना चाहते हैं)।

इन्हें आपको थोड़ी देर व्यस्त रखना चाहिए। :-)


Here is something that worked for me once. I can't say that it will work for you. I had code on the lines of

switch(num) {
   case 1: result = f1(param); break;
   case 2: result = f2(param); break;
   //...
}

Then I got a serious performance boost when I changed it to

// init:
funcs[N] = {f1, f2 /*...*/};
// later in the code:
result = (funcs[num])(param);

Perhaps someone here can explain the reason the latter version is better. I suppose it has something to do with the fact that there are no conditional branches there.


If you work on big matrices for instance, consider tiling your loops to improve the locality. This often leads to dramatic improvements. You can use VTune/PTU to monitor the L2 cache misses.


My current project is a media server, with multi thread processing (C++ language). It's a time critical application, once low performance functions could cause bad results on media streaming like lost of sync, high latency, huge delays and so.

The strategy i usually use to grantee the best performance possible is to minimize the amount of heavy operational system calls that allocate or manage resources like memory, files, sockets and so.

At first i wrote my own STL, network and file manage classes.

All my containers classes ("MySTL") manage their own memory blocks to avoid multiple alloc (new) / free (delete) calls. The objects released are enqueued on a memory block pool to be reused when needed. On that way i improve performance and protect my code against memory fragmentation.

The parts of the code that need to access lower performance system resources (like files, databases, script, network write) i use separate threads for them. But not one thread for each unit (like not 1 thread for each socket), if so the operational system would lose performance while managing a high number of threads. So you can group objects of same classes to be processed on a separate thread if possible.

For example, if you have to write data to a network socket, but the socket write buffer is full, i save the data on a sendqueue buffer (which shares memory with all sockets together) to be sent on a separate thread as soon as the sockets become writeable again. At this way your main threads should never stop processing on a blocked state waiting for the operational system frees a specific resource. All the buffers released are saved and reused when needed.

After all a profile tool would be welcome to look for program bottles and shows which algorithms should be improved.

i got succeeded using that strategy once i have servers running like 500+ days on a linux machine without rebooting, with thousands users logging everyday.

[02:01] -alpha.ip.tv- Uptime: 525days 12hrs 43mins 7secs


i'm surprised no one has mentioned these two:

  • Link time optimization clang and g++ from 4.5 on support link time optimizations. I've heard that on g++ case, the heuristics is still pretty inmature but it should improve quickly since the main architecture is laid out.

    Benefits range from inter procedural optimizations at object file level, including highly sought stuff like inling of virtual calls (devirtualization)

  • Project inlining this might seem to some like very crude approach, but it is that very crudeness which makes it so powerful: this amounts at dumping all your headers and .cpp files into a single, really big .cpp file and compile that; basically it will give you the same benefits of link-time optimization in your trip back to 1999. Of course, if your project is really big, you'll still need a 2010 machine; this thing will eat your RAM like there is no tomorrow. However, even in that case, you can split it in more than one no-so-damn-huge .cpp file





x86