c++ - कोडिंग प्रैक्टिस जो एक तेज प्रोग्राम बनाने के लिए कंपाइलर/ऑप्टिमाइज़र को सक्षम बनाता है




performance optimization (20)

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

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

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

क्या कोडिंग प्रथाएं उपलब्ध हैं जो कंपाइलर / ऑप्टिमाइज़र को तेज़ कोड उत्पन्न करने में सक्षम कर सकती हैं?

  • आपके द्वारा उपयोग किए जाने वाले प्लेटफ़ॉर्म और कंपाइलर की पहचान करने की सराहना की जाएगी।
  • तकनीक क्यों काम करती है?
  • नमूना कोड प्रोत्साहित किया जाता है।

यहां एक संबंधित प्रश्न है

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

[संपादित करें] ऑफ़सेट संबंधित लिंक


सामान्य अनुकूलन

यहां मेरे कुछ पसंदीदा अनुकूलन के रूप में। मैंने वास्तव में निष्पादन के समय में वृद्धि की है और इनका उपयोग करके प्रोग्राम आकार कम कर दिया है।

inline या मैक्रोज़ के रूप में छोटे कार्यों की घोषणा करें

फ़ंक्शन (या विधि) के लिए प्रत्येक कॉल ओवरहेड में घुमाता है, जैसे स्टैक पर चर को धक्का देना। कुछ कार्यों में वापसी पर एक ओवरहेड भी हो सकता है। संयुक्त ओवरहेड की तुलना में एक अक्षम कार्य या विधि में इसकी सामग्री में कम विवरण हैं। ये इनलाइनिंग के लिए अच्छे उम्मीदवार हैं, चाहे वह #define मैक्रोज़ या inline फ़ंक्शंस हों। (हाँ, मुझे पता है कि inline केवल एक सुझाव है, लेकिन इस मामले में मैं इसे संकलक के लिए अनुस्मारक मानता हूं।)

मृत और अनावश्यक कोड निकालें

यदि कोड का उपयोग नहीं किया जाता है या प्रोग्राम के नतीजे में योगदान नहीं देता है, तो इससे छुटकारा पाएं।

एल्गोरिदम के डिजाइन को सरल बनाएं

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

लूप अनोलिंग

प्रत्येक पाश में वृद्धि और समापन जांच का एक ओवरहेड होता है। प्रदर्शन कारक का अनुमान प्राप्त करने के लिए, ओवरहेड में निर्देशों की संख्या गिनें (न्यूनतम 3: वृद्धि, चेक, लूप की गोटो प्रारंभ) और लूप के अंदर बयानों की संख्या से विभाजित करें। संख्या जितनी कम होगी उतनी ही कम होगी।

संपादित करें: लूप अनोलिंग का उदाहरण प्रदान करें :

unsigned int sum = 0;
for (size_t i; i < BYTES_TO_CHECKSUM; ++i)
{
    sum += *buffer++;
}

अनोलिंग के बाद:

unsigned int sum = 0;
size_t i = 0;
**const size_t STATEMENTS_PER_LOOP = 8;**
for (i = 0; i < BYTES_TO_CHECKSUM; **i = i / STATEMENTS_PER_LOOP**)
{
    sum += *buffer++; // 1
    sum += *buffer++; // 2
    sum += *buffer++; // 3
    sum += *buffer++; // 4
    sum += *buffer++; // 5
    sum += *buffer++; // 6
    sum += *buffer++; // 7
    sum += *buffer++; // 8
}
// Handle the remainder:
for (; i < BYTES_TO_CHECKSUM; ++i)
{
    sum += *buffer++;
}

इस लाभ में, एक माध्यमिक लाभ प्राप्त होता है: प्रोसेसर को निर्देश कैश को फिर से लोड करने से पहले अधिक कथन निष्पादित किए जाते हैं।

जब मैंने 32 कथनों के लिए लूप को अनलॉक किया तो मेरे पास आश्चर्यजनक परिणाम हुए। यह बाधाओं में से एक था क्योंकि कार्यक्रम को 2 जीबी फ़ाइल पर चेकसम की गणना करनी थी। यह अनुकूलन 1 घंटे से 5 मिनट तक बेहतर प्रदर्शन पढ़ने के ब्लॉक के साथ संयुक्त है। लूप अनोलिंग ने भी असेंबली भाषा में उत्कृष्ट प्रदर्शन प्रदान किया, मेरा memcpy संकलक के memcpy से बहुत तेज था। - टीएम

if बयानों में कमी

प्रोसेसर शाखाओं, या कूद से नफरत करते हैं, क्योंकि यह प्रोसेसर को निर्देशों की अपनी कतार पुनः लोड करने के लिए मजबूर करता है।

बूलियन अंकगणितीय ( संपादित: कोड खंड के लिए लागू कोड प्रारूप, जोड़ा उदाहरण)

बूलियन असाइनमेंट में बयान में कनवर्ट करें। कुछ प्रोसेसर ब्रांचिंग के बिना सशर्त रूप से निर्देश निष्पादित कर सकते हैं:

bool status = true;
status = status && /* first test */;
status = status && /* second test */;

लॉजिकल एंड ऑपरेटर ( && ) की छोटी सर्किटिंग status false होने पर परीक्षणों को निष्पादित करने से रोकती है।

उदाहरण:

struct Reader_Interface
{
  virtual bool  write(unsigned int value) = 0;
};

struct Rectangle
{
  unsigned int origin_x;
  unsigned int origin_y;
  unsigned int height;
  unsigned int width;

  bool  write(Reader_Interface * p_reader)
  {
    bool status = false;
    if (p_reader)
    {
       status = p_reader->write(origin_x);
       status = status && p_reader->write(origin_y);
       status = status && p_reader->write(height);
       status = status && p_reader->write(width);
    }
    return status;
};

लूप के बाहर फैक्टर परिवर्तनीय आवंटन

यदि एक लूप के अंदर फ्लाई पर एक चर बनाया गया है, तो लूप से पहले सृजन / आवंटन को स्थानांतरित करें। ज्यादातर मामलों में, परिवर्तनीय को प्रत्येक पुनरावृत्ति के दौरान आवंटित करने की आवश्यकता नहीं होती है।

लूप के बाहर फैक्टर निरंतर अभिव्यक्तियां

यदि गणना या परिवर्तनीय मान लूप इंडेक्स पर निर्भर नहीं है, तो इसे लूप के बाहर (पहले) ले जाएं।

ब्लॉक में I / O

बड़े हिस्से (ब्लॉक) में डेटा पढ़ें और लिखें। जितना बड़ा उतना अच्छा। उदाहरण के लिए, एक समय में एक ऑक्टेट को पढ़ने से एक पढ़ने के साथ 1024 ऑक्टेट पढ़ने से कम कुशल होता है।
उदाहरण:

static const char  Menu_Text[] = "\n"
    "1) Print\n"
    "2) Insert new customer\n"
    "3) Destroy\n"
    "4) Launch Nasal Demons\n"
    "Enter selection:  ";
static const size_t Menu_Text_Length = sizeof(Menu_Text) - sizeof('\0');
//...
std::cout.write(Menu_Text, Menu_Text_Length);

इस तकनीक की दक्षता को दृष्टि से प्रदर्शित किया जा सकता है। :-)

निरंतर डेटा के लिए printf परिवार का उपयोग न करें

निरंतर डेटा ब्लॉक लिखने का उपयोग कर आउटपुट हो सकता है। स्वरूपित लेखन प्रारूप स्वरूपण या प्रोसेसिंग स्वरूपण आदेशों के लिए पाठ स्कैन करने में समय बर्बाद कर देगा। उपरोक्त कोड उदाहरण देखें।

स्मृति के लिए प्रारूप, फिर लिखें

एकाधिक sprintf का उपयोग कर एक char सरणी के लिए प्रारूप, फिर fwrite उपयोग करें। यह डेटा लेआउट को "निरंतर खंड" और चर खंडों में विभाजित करने की अनुमति भी देता है। मेल-विलय के बारे में सोचें।

स्थिरांक के रूप में निरंतर पाठ (स्ट्रिंग अक्षर) घोषित करें

जब static बिना चर घोषित किया जाता है, तो कुछ कंपाइलर स्टैक पर स्थान आवंटित कर सकते हैं और डेटा को रोम से कॉपी कर सकते हैं। ये दो अनावश्यक संचालन हैं। यह static उपसर्ग का उपयोग कर तय किया जा सकता है।

अंत में, संकलक की तरह कोड होगा

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


  1. Use the most local scope possible for all variable declarations.

  2. Use const whenever possible

  3. Dont use register unless you plan to profile both with and without it

The first 2 of these, especially #1 one help the optimizer analyze the code. It will especially help it to make good choices about what variables to keep in registers.

Blindly using the register keyword is as likely to help as hurt your optimization, It's just too hard to know what will matter until you look at the assembly output or profile.

There are other things that matter to getting good performance out of code; designing your data structures to maximize cache coherency for instance. But the question was about the optimizer.


अनुकूलक वास्तव में आपके प्रोग्राम के प्रदर्शन के नियंत्रण में नहीं है, आप हैं। उपयुक्त एल्गोरिदम और संरचनाओं और प्रोफाइल, प्रोफाइल, प्रोफाइल का प्रयोग करें।

उस ने कहा, आपको किसी अन्य फ़ाइल में एक फ़ाइल से एक छोटे से फ़ंक्शन पर आंतरिक-लूप नहीं होना चाहिए, क्योंकि यह इसे रेखांकित होने से रोकता है।

यदि संभव हो तो एक चर के पते को लेने से बचें। एक सूचक के लिए पूछना "मुक्त" नहीं है क्योंकि इसका मतलब है कि परिवर्तनीय को स्मृति में रखा जाना चाहिए। यदि आप पॉइंटर्स से बचते हैं तो भी एक सरणी रजिस्टरों में रखी जा सकती है - यह वेक्टरिंग के लिए आवश्यक है।

जो अगले बिंदु पर जाता है, ^ # $ @ मैनुअल पढ़ें ! यदि आप यहां __restrict__ हैं और __attribute__( __aligned__ ) __restrict__ हैं तो जीसीसी सादे सी कोड को __attribute__( __aligned__ ) कर सकता है। यदि आप अनुकूलक से कुछ विशिष्ट चाहते हैं, तो आपको विशिष्ट होना पड़ सकता है।


अपने कोड में यथासंभव कॉन्स शुद्धता का उपयोग करें। यह संकलक को बेहतर अनुकूलित करने की अनुमति देता है।

इस दस्तावेज़ में अन्य अनुकूलन युक्तियों के भार हैं: सीपीपी अनुकूलन (हालांकि थोड़ा पुराना दस्तावेज़)

पर प्रकाश डाला:

  • कन्स्ट्रक्टर प्रारंभिक सूचियों का उपयोग करें
  • उपसर्ग ऑपरेटरों का उपयोग करें
  • स्पष्ट रचनाकारों का उपयोग करें
  • इनलाइन फ़ंक्शन
  • अस्थायी वस्तुओं से बचें
  • आभासी कार्यों की लागत से अवगत रहें
  • संदर्भ पैरामीटर के माध्यम से वस्तुओं को वापस करें
  • प्रति वर्ग आवंटन पर विचार करें
  • एसएलएल कंटेनर आवंटकों पर विचार करें
  • 'खाली सदस्य' अनुकूलन
  • आदि

कंपाइलर को तेजी से कोड बनाने में मदद करने के लिए कोडिंग अभ्यास यहां दिया गया है- कोई भी भाषा, कोई प्लेटफॉर्म, कोई कंपाइलर, कोई समस्या:

किसी भी चालाक चाल का उपयोग करें जो मजबूती देता है, या यहां तक ​​कि प्रोत्साहित करता है, संकलक मेमोरी (कैश और रजिस्टरों समेत) में वैरिएबल को बाहर रखने के लिए सबसे अच्छा लगता है। पहले एक प्रोग्राम लिखें जो सही और रखरखाव योग्य है।

अगला, अपना कोड प्रोफाइल करें।

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

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


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

संदर्भ या पॉइंटर द्वारा डेटा के साथ काम करते समय स्थानीय चर में खींचें, अपना काम करें, और उसके बाद इसे कॉपी करें। (जब तक आपके पास कोई अच्छा कारण न हो)

0 के खिलाफ लगभग मुफ्त तुलना का उपयोग करें कि गणित या तर्क संचालन करते समय अधिकांश प्रोसेसर आपको देते हैं। आप लगभग हमेशा == 0 और <0 के लिए ध्वज प्राप्त करते हैं, जिससे आप आसानी से 3 स्थितियां प्राप्त कर सकते हैं:

x= f();
if(!x){
   a();
} else if (x<0){
   b();
} else {
   c();
}

अन्य स्थिरांक के परीक्षण के मुकाबले लगभग हमेशा सस्ता है।

एक और चाल रेंज परीक्षण में एक तुलना को खत्म करने के लिए घटाव का उपयोग करना है।

#define FOO_MIN 8
#define FOO_MAX 199
int good_foo(int foo) {
    unsigned int bar = foo-FOO_MIN;
    int rc = ((FOO_MAX-FOO_MIN) < bar) ? 1 : 0;
    return rc;
} 

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

सी में स्ट्रिंग फ़ंक्शंस का उपयोग करते समय (strcpy, memcpy, ...) याद रखें कि वे क्या लौटते हैं - गंतव्य! आप पॉइंटर की अपनी प्रति को 'भूल' से अक्सर बेहतर कोड प्राप्त कर सकते हैं और इन कार्यों की वापसी से इसे वापस ले जा सकते हैं।

कभी भी वही काम वापस करने के लिए oppurtunity को अनदेखा न करें जिसे आपने वापस बुलाया था। कंपाइलर्स इसे चुनने में इतने महान नहीं हैं कि:

foo_t * make_foo(int a, int b, int c) {
        foo_t * x = malloc(sizeof(foo));
        if (!x) {
             // return NULL;
             return x; // x is NULL, already in the register used for returns, so duh
        }
        x->a= a;
        x->b = b;
        x->c = c;
        return x;
}

बेशक, आप उस पर तर्क को उलट सकते हैं अगर केवल एक वापसी बिंदु है।

(चालें जिन्हें मैंने बाद में याद किया)

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


मैंने एक अनुकूलित सी संकलक लिखा और यहां विचार करने के लिए कुछ बहुत ही उपयोगी चीजें हैं:

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

  2. यदि वैश्विक चर का उपयोग किया जाता है, तो संभव होने पर उन्हें स्थिर और निरंतर चिह्नित करें। यदि वे एक बार (केवल पढ़ने के लिए) प्रारंभ होते हैं, तो प्रारंभिक कॉन्स int VAL [] = {1,2,3,4} जैसे प्रारंभकर्ता सूची का उपयोग करना बेहतर होता है, अन्यथा संकलक शायद यह नहीं खोज सकता कि चर वास्तव में स्थिरांक प्रारंभ किए गए हैं और स्थिरांक से स्थिरांक के साथ लोड को प्रतिस्थापित करने में विफल हो जाएगा।

  3. कभी भी लूप के अंदर एक गोटो का उपयोग न करें, लूप को अब अधिकांश कंपाइलर्स द्वारा पहचाना नहीं जाएगा और सबसे महत्वपूर्ण अनुकूलन लागू नहीं होंगे।

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

  5. जब भी संभव हो, पॉइंटर्स के बजाय सरणी का प्रयोग करें, खासकर लूप के अंदर (एक [i])। एक सरणी आमतौर पर उपनाम विश्लेषण के लिए अधिक जानकारी प्रदान करती है और कुछ अनुकूलन के बाद भी वही कोड उत्पन्न किया जाएगा (उत्सुक होने पर लूप शक्ति में कमी की खोज करें)। इससे लूप-इनवेरिएंट कोड गति को लागू करने का मौका भी बढ़ जाता है।

  6. बड़े कार्यों या बाहरी कार्यों के लिए लूप कॉल के बाहर उठाने का प्रयास करें जिनके दुष्प्रभाव नहीं हैं (वर्तमान लूप पुनरावृत्ति पर निर्भर न करें)। छोटे कार्यों को कई मामलों में रेखांकित किया जाता है या घुसपैठ करने वाले इंट्राइनिक्स में परिवर्तित किया जाता है, लेकिन बड़े कार्यों को कंपाइलर के साइड इफेक्ट्स के लिए प्रतीत होता है जब वे वास्तव में नहीं करते हैं। बाहरी कार्यों के लिए साइड इफेक्ट्स पूरी तरह से अज्ञात हैं, मानक लाइब्रेरी से कुछ फ़ंक्शंस के अपवाद के साथ जिन्हें कभी-कभी कुछ कंपाइलर्स द्वारा मॉडलिंग किया जाता है, जिससे लूप-इनवेरिएंट कोड गति संभव हो जाता है।

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

  8. एक स्विच का उपयोग करना एक परीक्षण करने से तेज है जैसे कि (ए || बी || ... || z)। पहले जांचें कि आपका कंपाइलर स्वचालित रूप से ऐसा करता है, कुछ करते हैं और यदि यह है तो यह और अधिक पठनीय है।


लोगों द्वारा लिखे गए कोड का विशाल बहुमत I / O बाध्य होगा (मुझे विश्वास है कि पिछले 30 वर्षों में मैंने जो भी कोड लिखा है, वह इतना बाध्य है), इसलिए अधिकांश लोगों के लिए अनुकूलक की गतिविधियां अकादमिक होंगी।

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


स्थानीय चरों को लिखें और आउटपुट तर्क नहीं! अलियासिंग मंदी के आसपास होने के लिए यह एक बड़ी मदद हो सकती है। उदाहरण के लिए, यदि आपका कोड कैसा दिखता है

void DoSomething(const Foo& foo1, const Foo* foo2, int numFoo, Foo& barOut)
{
    for (int i=0; i<numFoo, i++)
    {
         barOut.munge(foo1, foo2[i]);
    }
}

संकलक को पता नहीं है कि foo1! = barOut, और इस प्रकार लूप के माध्यम से प्रत्येक बार foo1 को फिर से लोड करना होगा। यह foo2 [i] भी नहीं पढ़ सकता है जब तक कि बार को लिखना समाप्त नहीं हो जाता है। आप प्रतिबंधित पॉइंटर्स के साथ गड़बड़ करना शुरू कर सकते हैं, लेकिन यह करने के लिए यह उतना ही प्रभावी (और बहुत स्पष्ट) है:

void DoSomethingFaster(const Foo& foo1, const Foo* foo2, int numFoo, Foo& barOut)
{
    Foo barTemp = barOut;
    for (int i=0; i<numFoo, i++)
    {
         barTemp.munge(foo1, foo2[i]);
    }
    barOut = barTemp;
}

यह मूर्खतापूर्ण लगता है, लेकिन संकलक स्थानीय चर के साथ बहुत अधिक स्मार्ट व्यवहार कर सकता है, क्योंकि यह किसी भी तर्क के साथ स्मृति में संभवतः ओवरलैप नहीं कर सकता है। यह आपको ड्रेस्ड लोड-हिट-स्टोर से बचने में मदद कर सकता है (इस धागे में फ्रांसिस बोइविन द्वारा उल्लिखित)।



Don't do the same work over and over again!

A common antipattern that I see goes along these lines:

void Function()
{
   MySingleton::GetInstance()->GetAggregatedObject()->DoSomething();
   MySingleton::GetInstance()->GetAggregatedObject()->DoSomethingElse();
   MySingleton::GetInstance()->GetAggregatedObject()->DoSomethingCool();
   MySingleton::GetInstance()->GetAggregatedObject()->DoSomethingReallyNeat();
   MySingleton::GetInstance()->GetAggregatedObject()->DoSomethingYetAgain();
}

The compiler actually has to call all of those functions all of the time. Assuming you, the programmer, knows that the aggregated object isn't changing over the course of these calls, for the love of all that is holy...

void Function()
{
   MySingleton* s = MySingleton::GetInstance();
   AggregatedObject* ao = s->GetAggregatedObject();
   ao->DoSomething();
   ao->DoSomethingElse();
   ao->DoSomethingCool();
   ao->DoSomethingReallyNeat();
   ao->DoSomethingYetAgain();
}

In the case of the singleton getter the calls may not be too costly, but it is certainly a cost (typically, "check to see if the object has been created, if it hasn't, create it, then return it). The more complicated this chain of getters becomes, the more wasted time we'll have.


For performance, focus first on writing maintenable code - componentized, loosely coupled, etc, so when you have to isolate a part either to rewrite, optimize or simply profile, you can do it without much effort.

Optimizer will help your program's performance marginally.


I have long suspected, but never proved that declaring arrays so that they hold a power of 2, as the number of elements, enables the optimizer to do a strength reduction by replacing a multiply by a shift by a number of bits, when looking up individual elements.


I was reminded of something that I encountered once, where the symptom was simply that we were running out of memory, but the result was substantially increased performance (as well as huge reductions in memory footprint).

The problem in this case was that the software we were using made tons of little allocations. Like, allocating four bytes here, six bytes there, etc. A lot of little objects, too, running in the 8-12 byte range. The problem wasn't so much that the program needed lots of little things, it's that it allocated lots of little things individually, which bloated each allocation out to (on this particular platform) 32 bytes.

Part of the solution was to put together an Alexandrescu-style small object pool, but extend it so I could allocate arrays of small objects as well as individual items. This helped immensely in performance as well since more items fit in the cache at any one time.

The other part of the solution was to replace the rampant use of manually-managed char* members with an SSO (small-string optimization) string. The minimum allocation being 32 bytes, I built a string class that had an embedded 28-character buffer behind a char*, so 95% of our strings didn't need to do an additional allocation (and then I manually replaced almost every appearance of char* in this library with this new class, that was fun or not). This helped a ton with memory fragmentation as well, which then increased the locality of reference for other pointed-to objects, and similarly there were performance gains.


If you've got small functions you call repeatedly, i have in the past got large gains by putting them in headers as "static inline". Function calls on the ix86 are surprisingly expensive.

Reimplementing recursive functions in a non-recursive way using an explicit stack can also gain a lot, but then you really are in the realm of development time vs gain.


Most modern compilers should do a good job speeding up tail recursion , because the function calls can be optimized out.

उदाहरण:

int fac2(int x, int cur) {
  if (x == 1) return cur;
  return fac2(x - 1, cur * x); 
}
int fac(int x) {
  return fac2(x, 1);
}

Of course this example doesn't have any bounds checking.

Late Edit

While I have no direct knowledge of the code; it seems clear that the requirements of using CTEs on SQL Server were specifically designed so that it can optimize via tail-end recursion.


One thing I've done is try to keep expensive actions to places where the user might expect the program to delay a bit. Overall performance is related to responsiveness, but isn't quite the same, and for many things responsiveness is the more important part of performance.

The last time I really had to do improvements in overall performance, I kept an eye out for suboptimal algorithms, and looked for places that were likely to have cache problems. I profiled and measured performance first, and again after each change. Then the company collapsed, but it was interesting and instructive work anyway.


Put small and/or frequently called functions at the top of the source file. That makes it easier for the compiler to find opportunities for inlining.


When DEC came out with its alpha processors, there was a recommendation to keep the number of arguments to a function under 7, as the compiler would always try to put up to 6 arguments in registers automatically.


You're getting good answers here, but they assume your program is pretty close to optimal to begin with, and you say

Assume that the program has been written correctly, compiled with full optimization, tested and put into production.

In my experience, a program may be written correctly, but that does not mean it is near optimal. It takes extra work to get to that point.

If I can give an example, this answer shows how a perfectly reasonable-looking program was made over 40 times faster by macro-optimization . Big speedups can't be done in every program as first written, but in many (except for very small programs), it can, in my experience.

After that is done, micro-optimization (of the hot-spots) can give you a good payoff.






optimization