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


116

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

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

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

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

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

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

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

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


7
कम्युनिटी विकी इमो के लिए एक अच्छा उम्मीदवार हो सकता है क्योंकि इस (दिलचस्प) सवाल का कोई 'एकल' निश्चित उत्तर नहीं है ...
क्रिस्टोफ़ीड

मुझे यह हर बार याद आती है। यह इंगित करने के लिए धन्यवाद।
EvilTeach

'बेहतर' से क्या आपका तात्पर्य केवल 'तेज' से है या आपके मन में उत्कृष्टता के अन्य मापदंड हैं?
उच्च प्रदर्शन मार्क

1
एक अच्छा रजिस्टर एलोकेटर लिखना बहुत कठिन है, विशेष रूप से, और रजिस्टर आवंटन प्रदर्शन और कोड आकार के लिए बिल्कुल आवश्यक है। registerवास्तव में खराब संकलकों का मुकाबला करके प्रदर्शन-संवेदनशील कोड को अधिक पोर्टेबल बना दिया।
पोटाटोस्वाटर

1
@EvilTeach: सामुदायिक विकि का अर्थ "कोई निश्चित उत्तर नहीं" है, जो व्यक्तिपरक टैग का पर्याय नहीं है। सामुदायिक विकी का मतलब है कि आप अपने पोस्ट को समुदाय को सौंपना चाहते हैं ताकि अन्य लोग इसे संपादित कर सकें। यदि आपको ऐसा महसूस नहीं होता है तो अपने सवालों पर ध्यान न दें।
जूलियट

जवाबों:


54

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

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] को तब तक नहीं पढ़ सकता है, जब तक कि barOut पर लिखना समाप्त न हो जाए। आप प्रतिबंधित पॉइंटर्स के साथ खिलवाड़ करना शुरू कर सकते हैं, लेकिन ऐसा करने के लिए यह उतना ही प्रभावी (और अधिक स्पष्ट) है:

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;
}

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


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

अधिकांश IDE डिफ़ॉल्ट रूप से स्थानीय चर प्रदर्शित करते हैं, इसलिए टाइपिंग कम है
EvilTeach

9
आप प्रतिबंधित बिंदुओं का उपयोग करके उस अनुकूलन को सक्षम भी कर सकते हैं
बेन वोइगट

4
@ - यह सच है, लेकिन मुझे लगता है कि यह रास्ता साफ है। इसके अलावा, यदि इनपुट और आउटपुट ओवरलैप हुआ, तो मेरा मानना ​​है कि परिणाम प्रतिबंधित पॉइंटर्स के साथ अनिर्दिष्ट है (शायद डिबग और रिलीज़ के बीच अलग-अलग व्यवहार करें), जबकि यह तरीका कम से कम सुसंगत होगा। मुझे गलत मत समझो, मुझे प्रतिबंधित का उपयोग करना पसंद है, लेकिन मुझे इसकी और भी अधिक आवश्यकता नहीं है।
celion

आपको बस यह आशा है कि फू ने एक कॉपी ऑपरेशन परिभाषित नहीं किया है जो डेटा के मेग के एक जोड़े को कॉपी करता है ;-)
स्किज़

76

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

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

इसके बाद अपना कोड प्रोफाइल करें।

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

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


20
Compiier Developers के पास सभी की तरह ही सीमित समय है। सभी अनुकूलन इसे संकलक में नहीं बनाएंगे। जैसा &बनाम %दो की शक्तियों (शायद ही कभी, अगर कभी, अनुकूलित किया है, लेकिन महत्वपूर्ण प्रदर्शन प्रभाव पड़ सकता है) के लिए। यदि आप प्रदर्शन के लिए एक चाल पढ़ते हैं, तो यह जानने का एकमात्र तरीका है कि यह काम करता है परिवर्तन करना और प्रभाव को मापना। कभी मत मानिए कि कंपाइलर आपके लिए कुछ अनुकूलित करेगा।
डेव जार्विस

22
& /% बहुत अधिक हमेशा अनुकूलित किया जाता है, साथ ही अन्य सबसे सस्ते-जैसे-मुक्त अंकगणितीय ट्रिक्स। जो चीज अनुकूलित नहीं होती है, वह दाहिने हाथ के ऑपरेंड के मामले में परिवर्तनशील होती है, जो कि हमेशा दो की शक्ति बनती है।
पोटाटोस्वाटर

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

17
निरंतर बिजली के- दो के लिए n, जीसीसी की जगह % nके साथ & (n-1) भी जब अनुकूलन अक्षम किया गया है । यह वास्तव में "शायद ही कभी, अगर कभी" नहीं है ...
पोरकुलस

12
नकारात्मक पूर्णांक विभाजन के लिए C के मुहावरेदार नियमों के कारण और इस प्रकार के हस्ताक्षर होने पर % CANNOT को अनुकूलित नहीं किया जा सकता है (0 की ओर गोल होता है और ऋणात्मक शेष होता है, बजाय नीचे और हमेशा सकारात्मक शेष होने के)। और ज्यादातर समय, अज्ञानी कोडर हस्ताक्षरित प्रकारों का उपयोग करते हैं ...
R .. GitHub STOP HELPING ICE

47

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

#define N 1000000;
int matrix[N][N] = { ... };

//awesomely fast
long sum = 0;
for(int i = 0; i < N; i++){
  for(int j = 0; j < N; j++){
    sum += matrix[i][j];
  }
}

//painfully slow
long sum = 0;
for(int i = 0; i < N; i++){
  for(int j = 0; j < N; j++){
    sum += matrix[j][i];
  }
}

कड़ाई से बोलना यह एक आशावादी मुद्दा नहीं है, बल्कि एक अनुकूलन मुद्दा है।
EvilTeach

10
यकीन है कि यह एक आशावादी मुद्दा है। लोग दशकों से स्वचालित लूप इंटरचेंज अनुकूलन के बारे में कागजात लिख रहे हैं।
फिल मिलर

20
@Potatoswatter आप किस बारे में बात कर रहे हैं? सी कंपाइलर वही कर सकता है जो वह करना चाहता है जब तक कि एक ही अंतिम परिणाम नहीं देखा जाता है, और वास्तव में जीसीसी 4.4 है -floop-interchangeजो कि एक आंतरिक और बाहरी लूप को फ्लिप करेगा यदि अनुकूलक इसे लाभदायक बनाता है।

2
हुह, अच्छी तरह से तुम वहाँ जाओ। सी शब्दार्थ अक्सर अलियासिंग मुद्दों द्वारा विवाहित होते हैं। मुझे लगता है कि यहाँ असली सलाह उस झंडे को पारित करने के लिए है!
पोटाटोस्वाटर

36

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

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

के रूप में छोटे कार्यों की घोषणा 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बयानों में कमी

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

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

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

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

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

उदाहरण:

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 परिवार का उपयोग न करें

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

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

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

के रूप में निरंतर पाठ (स्ट्रिंग शाब्दिक) की घोषणा करें static const

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

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

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


2
दिलचस्प आप एक उदाहरण प्रदान कर सकते हैं जहां आपको कुछ छोटे बयानों के साथ बेहतर कोड मिला, बजाय एक बड़ा एक के। क्या आप बूलियन्स का उपयोग करते हुए, एक को फिर से लिखने का एक उदाहरण दिखा सकते हैं। आम तौर पर, मैं लूप को संकलक के पास छोड़ देता हूं, क्योंकि यह संभवतः कैश आकार के लिए बेहतर अनुभव है। मैं स्प्रिंटफिंग के विचार के बारे में थोड़ा हैरान हूं, फिर लिखावट। मुझे लगता है कि fprintf वास्तव में हुड के तहत करता है। क्या आप यहां थोड़ा और विस्तार दे सकते हैं?
EvilTeach

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

3
मुझे एक बार लूप रोल करके एक महत्वपूर्ण प्रदर्शन में सुधार हुआ। तब मुझे पता चला कि कुछ अप्रत्यक्ष रूप से इसका उपयोग करके इसे कैसे लुढ़काना है, और कार्यक्रम काफ़ी तेजी से हुआ। (प्रोफाइलिंग ने इस विशेष फ़ंक्शन को 60-80% रनटाइम दिखाया, और मैंने प्रदर्शन को पहले और बाद में सावधानीपूर्वक परीक्षण किया।) मेरा मानना ​​है कि सुधार बेहतर स्थानीयता के कारण था, लेकिन मैं इसके बारे में पूरी तरह से निश्चित नहीं हूं।
डेविड थॉर्नले

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

26

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

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

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

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


14
यह एक अच्छा जवाब है, लेकिन ध्यान दें कि पूरे-कार्यक्रम अनुकूलन अधिक लोकप्रिय हो रहे हैं, और वास्तव में अनुवाद इकाइयों में इनलाइन फ़ंक्शन कर सकते हैं।
फिल मिलर

1
@ नोवेलोक्रेट येप - कहने की ज़रूरत नहीं कि पहली बार जब मैंने कुछ देखा तो बहुत आश्चर्यचकित रह A.cगया B.c
जोनाथन रेनहार्ट

18

अधिकांश आधुनिक प्रोसेसर पर, सबसे बड़ी अड़चन मेमोरी है।

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

कैश-मिस: वास्तव में सुनिश्चित नहीं है कि आप कंपाइलर की मदद कैसे कर सकते हैं क्योंकि यह ज्यादातर एल्गोरिदम है, लेकिन मेमोरी को प्रीचेट करने के लिए आंतरिक हैं।

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


4
लोड-हिट-स्टोर और विभिन्न रजिस्टर प्रकारों के लिए +1। मुझे यकीन नहीं है कि x86 में यह कितना बड़ा सौदा है, लेकिन वे पावरपीसी (जैसे Xbox360 और Playstation3) पर विनाशकारी हैं।
celion

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

11

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

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


7
मैं अजीब होने की बात कबूल करता हूं - मैं बड़े वैज्ञानिक नंबर-क्रंचिंग कोड पर काम करता हूं जो मेमोरी-बैंडविड्थ बाउंड होते हैं। कार्यक्रमों की सामान्य आबादी के लिए मैं नील से सहमत हूं।
उच्च प्रदर्शन मार्क

6
सच; लेकिन उस समय का एक बहुत बड़ा मैं / ओ-बाउंड कोड आजकल उन भाषाओं में लिखा जाता है जो व्यावहारिक रूप से निराशावादी हैं - ऐसी भाषाएँ जिनके पास कंपाइलर भी नहीं हैं। मुझे संदेह है कि जिन क्षेत्रों में अभी भी C और C ++ का उपयोग किया जाता है, वे ऐसे क्षेत्र होंगे जहां किसी चीज़ को अनुकूलित करना अधिक महत्वपूर्ण होगा (CPU उपयोग, मेमोरी उपयोग, कोड आकार ...)
पोर्कुलस

3
मैंने पिछले 30 वर्षों में से अधिकांश पर बहुत कम I / O के साथ कोड पर काम किया है। डेटाबेस करने के लिए 2 साल बचाएं। ग्राफिक्स, कंट्रोल सिस्टम, सिमुलेशन - इसमें से कोई भी I / O बाध्य नहीं है। यदि I / O अधिकांश लोगों की अड़चन थी, तो हम Intel और AMD पर अधिक ध्यान नहीं देंगे।
फकहलर 16

2
हाँ, मैं वास्तव में इस तर्क को नहीं खरीदता- अन्यथा हम (मेरी नौकरी पर) गणना के समय की तलाश में ऐसे तरीकों की तलाश में नहीं होते, जो I / O कर रहे हों। इसके अलावा- I / O बाउंड सॉफ्टवेयर का अधिकांश हिस्सा I / O बाउंड हो चुका है क्योंकि I / O को धीरे-धीरे किया गया था; यदि कोई एक्सेस पैटर्न का अनुकूलन करता है (जैसे मेमोरी के साथ), तो किसी को प्रदर्शन में भारी लाभ मिल सकता है।
डैश-टॉम-बैंग

3
मुझे हाल ही में पता चला है कि C ++ भाषा में लिखा लगभग कोई कोड I / O बाध्य नहीं है। यकीन है, अगर आप थोक डिस्क स्थानांतरण के लिए एक ओएस फ़ंक्शन कह रहे हैं, तो आपका धागा I / O प्रतीक्षा में जा सकता है (लेकिन कैशिंग के साथ, यहां तक ​​कि पूछताछ भी)। लेकिन सामान्य I / O लाइब्रेरी फ़ंक्शंस, जिन्हें हर कोई सुझाता है क्योंकि वे मानक और पोर्टेबल हैं, वास्तव में आधुनिक डिस्क तकनीक (यहां तक ​​कि मामूली कीमत वाले सामान) की तुलना में बुरी तरह से धीमा हैं। सबसे अधिक संभावना है, मैं / ओ केवल अड़चन है अगर आप बस कुछ बाइट्स लिखने के बाद डिस्क पर सभी तरह से फ्लश कर रहे हैं। OTOH, UI एक अलग मामला है, हम इंसान धीमे हैं।
बेन Voigt

11

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

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

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

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

8
ज्यादा नहीं, शायद ही कभी। यह वास्तविक शुद्धता में सुधार करता है, हालांकि।
पोटाटोस्वर

5
C और C ++ में कंपाइलर ऑप्टिमाइज़ करने के लिए कॉन्स्ट का इस्तेमाल नहीं कर सकता है क्योंकि इसे कास्टिंग करना अच्छी तरह से परिभाषित व्यवहार है।
dsimcha

+1: const एक अच्छा उदाहरण है जो सीधे संकलित कोड को प्रभावित करेगा। @ @ dsimcha की टिप्पणी - एक अच्छा संकलक यह देखने के लिए परीक्षण करेगा कि क्या ऐसा होता है। बेशक, एक अच्छा संकलक ऐसे तत्वों को "ढूंढेगा" जो वैसे भी घोषित नहीं किए गए हैं ...
होगन

@dsimcha: एक const और restrict योग्य सूचक को बदलना , हालांकि, अपरिभाषित है। तो एक कंपाइलर ऐसे मामले में अलग तरह से अनुकूलन कर सकता है।
डायट्रिच एप्प

6
@dsimcha constएक constसंदर्भ या constपॉइंटर पर एक गैर- constऑब्जेक्ट के लिए दूर कास्टिंग अच्छी तरह से परिभाषित है। एक वास्तविक constवस्तु को संशोधित करना (अर्थात constमूल रूप से घोषित ) नहीं है।
स्टीफन लिन

9

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

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

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;
} 

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

जब c (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;
}

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

(बाद में याद किए गए ट्रिक्स)

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


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

उपरोक्त उदाहरण में, b () को नहीं बुलाया जा सकता है क्योंकि यदि (x <0) तो a () कहा जाएगा।
EvilTeach

@EvilTeach नहीं यह नहीं होगा। (
A

@nategoose। यदि x -3 है तो! x सत्य है।
EvilTeach

@ EvilTeach इन C 0 गलत है और बाकी सब कुछ सच है, इसलिए -3 सच है, इसलिए -3 गलत है
nategoose

9

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

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

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

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

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

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

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

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

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


7

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

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


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

7

एक गूंगा थोड़ा टिप, लेकिन एक जो आपको गति और कोड की कुछ सूक्ष्म मात्रा में बचाएगा।

हमेशा एक ही क्रम में फ़ंक्शन तर्क पास करें।

यदि आपके पास f_1 (x, y, z) है जो f_2 को कॉल करता है, तो f_2 को f_2 (x, y, z) घोषित करें। इसे f_2 (x, z, y) घोषित न करें।

इसका कारण यह है कि C / C ++ प्लेटफॉर्म ABI (AKA कॉलिंग कन्वेंशन) विशेष रजिस्टरों और स्टैक स्थानों में तर्क पारित करने का वादा करता है। जब तर्क पहले से ही सही रजिस्टरों में होते हैं तो उन्हें उन्हें इधर-उधर करने की जरूरत नहीं होती।

असंतुष्ट कोड को पढ़ते हुए मैंने कुछ हास्यास्पद रजिस्टर में फेरबदल देखा है क्योंकि लोग इस नियम का पालन नहीं करते थे।


2
विशेष रजिस्टरों या स्टैक स्थानों में से गुजरने के बारे में न तो सी और न ही सी ++ कोई गारंटी देते हैं, या यहां तक ​​कि उल्लेख भी करते हैं। यह एबीआई (जैसे लिनक्स ईएलएफ) है जो पैरामीटर पासिंग के विवरण को निर्धारित करता है।
Emmet

5

दो कोडिंग टेकनीक मैंने उपरोक्त सूची में नहीं देखी:

एक अद्वितीय स्रोत के रूप में कोड लिखकर बायपास लिंकर

जबकि अलग संकलन समय संकलन के लिए वास्तव में अच्छा है, जब आप अनुकूलन की बात करते हैं तो यह बहुत बुरा होता है। मूल रूप से संकलक संकलन इकाई से परे अनुकूलन नहीं कर सकता है, जो लिंकर आरक्षित डोमेन है।

लेकिन अगर आप अपने कार्यक्रम को अच्छी तरह से डिजाइन करते हैं तो आप इसे एक अनोखे सामान्य स्रोत के माध्यम से भी संकलित कर सकते हैं। इसके बजाय Unit1.c और unit2.c को संकलित करने के बजाय दोनों ऑब्जेक्ट्स को लिंक करें, all.c को संकलित करें जो केवल #include unit1.c और unit2.c को संकलित करता है। इस प्रकार आप सभी संकलक अनुकूलन से लाभान्वित होंगे।

यह हेडर केवल C ++ में प्रोग्राम लिखना पसंद करता है (और सी में करना भी आसान है)।

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

इस सरल तकनीक का उपयोग करते हुए मैंने कुछ कार्यक्रम किए जो मैंने दस गुना तेज लिखा था!

रजिस्टर कीवर्ड की तरह, यह ट्रिक भी जल्द ही पुरानी हो सकती है। लिंकर के माध्यम से अनुकूलन कंपाइलर जीसीसी द्वारा समर्थित होना शुरू होता है : लिंक समय अनुकूलन

छोरों में परमाणु कार्यों को अलग करें

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

इस एक के साथ सावधान रहें (इस चाल का उपयोग करके प्रोफ़ाइल प्रदर्शन या नहीं) रजिस्टर का उपयोग करने के साथ-साथ यह बेहतर प्रदर्शनों की तुलना में कम प्रदर्शन दे सकता है।


2
हां, अब तक, LTO ने इस पोस्ट के पहले भाग को बेमानी बना दिया है और शायद बुरी सलाह।
अंडरस्कोर_ड

@underscore_d: अभी भी कुछ मुद्दे हैं (ज्यादातर निर्यात किए गए प्रतीकों की दृश्यता से संबंधित हैं), लेकिन एक मात्र प्रदर्शन के दृष्टिकोण से शायद कोई और नहीं है।
क्रिश

4

मैंने वास्तव में इसे SQLite में देखा है और वे दावा करते हैं कि इससे प्रदर्शन में वृद्धि होती है ~ 5%: अपने सभी कोड को एक फ़ाइल में रखें या इसके बराबर करने के लिए प्रीप्रोसेसर का उपयोग करें। इस तरह ऑप्टिमाइज़र के पास पूरे प्रोग्राम की पहुँच होगी और वह अधिक इंटरप्रोडेक्शियल ऑप्टिमाइज़ेशन कर सकता है।


5
स्रोत में निकट शारीरिक निकटता में एक साथ उपयोग किए जाने वाले कार्यों की संभावना बढ़ जाती है कि वे ऑब्जेक्ट फ़ाइलों में एक दूसरे के पास और आपके निष्पादन योग्य में एक दूसरे के पास होंगे। निर्देशों का यह बेहतर इलाका चलने के दौरान अनुदेश कैश मिस से बचने में मदद कर सकता है।
paxos1977

AIX कंपाइलर में उस व्यवहार को प्रोत्साहित करने के लिए एक कंपाइलर स्विच होता है -qipa = = <suboptions_list>] | -नकोइपा इंटरप्रेडेक्टोरल एनालिसिस (आईपीए) के रूप में ज्ञात अनुकूलन के एक वर्ग को चालू या अनुकूलित करता है।
EvilTeach

4
बेस्ट को विकसित करने का एक तरीका है जिसके लिए इसकी आवश्यकता नहीं है। इस तथ्य को संयुक्त राष्ट्र के मॉड्यूलर कोड लिखने के बहाने के रूप में उपयोग करने से कुल मिलाकर कोड धीमा होगा और रखरखाव की समस्याएं हो सकती हैं।
होगन

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

@Wallacoloo सुनिश्चित करने के लिए, यह बाहर की तारीख है। एफडब्ल्यूआईडब्ल्यू, मैंने आज पहली बार जीसीसी के एलटीओ का इस्तेमाल किया, और - बाकी सभी के बराबर -O3- इसने मेरे कार्यक्रम के मूल आकार का 22% नष्ट कर दिया। (यह सीपीयू-बाउंड नहीं है, इसलिए मुझे गति के बारे में कहने के लिए बहुत कुछ नहीं मिला है।)
अंडरस्कोर_ड

4

अधिकांश आधुनिक संकलक को पूंछ पुनरावृत्ति को गति देने वाला एक अच्छा काम करना चाहिए , क्योंकि फ़ंक्शन कॉल को अनुकूलित किया जा सकता है।

उदाहरण:

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);
}

बेशक इस उदाहरण की कोई सीमा जाँच नहीं है।

लेट एडिट

जबकि मुझे कोड का कोई सीधा ज्ञान नहीं है; यह स्पष्ट लगता है कि SQL सर्वर पर CTEs का उपयोग करने की आवश्यकताओं को विशेष रूप से डिज़ाइन किया गया था ताकि यह पूंछ के अंत पुनरावृत्ति के माध्यम से अनुकूलित कर सके।


1
सवाल यह है कि सी। के बारे में पूंछ-पुनरावृत्ति को दूर नहीं करता है, इसलिए पूंछ या अन्य पुनरावृत्ति, स्टैक को झटका दे सकती है यदि पुनरावृत्ति बहुत गहरी हो जाती है।
तादाद

1
मैंने एक गोटो का उपयोग करके, कॉलिंग कन्वेंशन के मुद्दे को टाल दिया है। इस तरह से ओवरहेड कम होता है।
EvilTeach

2
@ होगन: यह मेरे लिए नया है। क्या आप किसी भी संकलक को इंगित कर सकते हैं जो ऐसा करता है? और आप यह कैसे सुनिश्चित कर सकते हैं कि यह वास्तव में इसका अनुकूलन करता है? यदि यह ऐसा करता है तो वास्तव में इसे सुनिश्चित करने की आवश्यकता है। यह कुछ ऐसा नहीं है जो आप उम्मीद करते हैं कि कंपाइलर ऑप्टिमाइज़र ऊपर उठाता है (जैसे inlining जो काम नहीं कर सकता है या नहीं)
Toad

6
@ होगन: मैं सही हूं। आप सही हैं कि Gcc और MSVC दोनों ही टेल रीसर्प्शन ऑप्टिमाइज़ेशन करते हैं।
तादाद

5
यह उदाहरण पूंछ पुनरावृत्ति नहीं है क्योंकि इसकी पुनरावर्ती कॉल अंतिम नहीं है, इसका गुणन है।
ब्रायन यंग

4

एक ही काम बार-बार न करें!

एक सामान्य एंटीपैटर जो मुझे दिखाई देता है, इन रेखाओं के साथ जाता है:

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

संकलक को वास्तव में उन सभी कार्यों को हर समय कॉल करना पड़ता है। आपको मानते हुए, प्रोग्रामर, जानता है कि सभी के प्यार के लिए, इन कॉलों के दौरान कुल मिलाकर वस्तु नहीं बदल रही है ...

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

सिंगलटन गेट्टर के मामले में कॉल बहुत महंगा नहीं हो सकता है, लेकिन यह निश्चित रूप से एक लागत है (आमतौर पर, यह देखने के लिए कि क्या ऑब्जेक्ट बनाया गया है, अगर यह नहीं बना है, तो इसे वापस लौटाएं)। अधिक जटिल यह गेटर्स की श्रृंखला बन जाती है, हमारे पास जितना अधिक समय बर्बाद होगा।


3
  1. सभी चर घोषणाओं के लिए संभव सबसे अधिक स्थानीय गुंजाइश का उपयोग करें।

  2. constजब भी संभव हो उपयोग करें

  3. जब तक आप इसके साथ और इसके बिना दोनों को प्रोफाइल करने की योजना नहीं बनाते तब तक रजिस्टर का उपयोग करें

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

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

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



3

मुझे उस चीज की याद दिलाई गई जो मैंने एक बार सामना की थी, जहां लक्षण बस यह था कि हम स्मृति से बाहर चल रहे थे, लेकिन परिणाम में काफी वृद्धि हुई थी (साथ ही स्मृति पदचिह्न में भारी कटौती)।

इस मामले में समस्या यह थी कि हम जिस सॉफ्टवेयर का उपयोग कर रहे थे, वह बहुत कम आवंटन का था। जैसे, चार बाइट्स यहाँ आवंटित करना, वहाँ छह बाइट्स इत्यादि, बहुत सी छोटी वस्तुएँ, 8-12 बाइट रेंज में चलना। समस्या इतनी अधिक नहीं थी कि कार्यक्रम में बहुत सारी छोटी चीजों की जरूरत थी, यह है कि यह बहुत सारी छोटी चीजों को व्यक्तिगत रूप से आवंटित करता है, जो कि प्रत्येक आवंटन को (इस विशेष मंच पर) 32 बाइट्स में ब्लोट करता है।

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

समाधान का दूसरा हिस्सा SSO (छोटे-स्ट्रिंग अनुकूलन) स्ट्रिंग के साथ मैन्युअल रूप से प्रबंधित चार * सदस्यों के बड़े पैमाने पर उपयोग को बदलने के लिए था। न्यूनतम आबंटन 32 बाइट्स होने के कारण, मैंने एक स्ट्रिंग क्लास बनाया, जिसमें एक चार * के पीछे 28-कैरेक्टर का बफर लगा हुआ था, इसलिए हमारे 95% स्ट्रिंग्स को अतिरिक्त आवंटन करने की आवश्यकता नहीं थी (और तब मैंने मैन्युअल रूप से लगभग हर उपस्थिति को बदल दिया था char * इस पुस्तकालय में इस नए वर्ग के साथ, यह मजेदार था या नहीं)। इसने मेमोरी के विखंडन के साथ एक टन की भी मदद की, जिसने तब अन्य इंगित वस्तुओं के लिए संदर्भ के इलाके में वृद्धि की, और इसी तरह प्रदर्शन लाभ प्राप्त किया।


3

इस उत्तर पर मैंने @ टिप्पणी से सीखी एक साफ-सुथरी तकनीक कंपाइलरों को कुछ वस्तुओं के अनुसार अलग-अलग वस्तुओं को वापस करते हुए भी कॉपी एलीशन करने की अनुमति देती है:

// before
BigObject a, b;
if(condition)
  return a;
else
  return b;

// after
BigObject a, b;
if(condition)
  swap(a,b);
return a;

2

यदि आपको छोटे-छोटे फ़ंक्शंस मिलते हैं जिन्हें आप बार-बार कॉल करते हैं, तो मुझे अतीत में "स्थिर इनलाइन" के रूप में हेडर लगाकर बड़े लाभ प्राप्त हुए हैं। Ix86 पर फ़ंक्शन कॉल आश्चर्यजनक रूप से महंगे हैं।

स्पष्ट स्टैक का उपयोग करके गैर-पुनरावर्ती तरीके से पुनरावर्ती कार्यों को लागू करना भी बहुत लाभ प्राप्त कर सकता है, लेकिन फिर आप वास्तव में विकास के समय बनाम लाभ के दायरे में हैं।


एक स्टैक में पुनरावृत्ति को परिवर्तित करना ompf.org पर एक ग्रहण किया गया अनुकूलन है, जो लोगों को रेअटराइटर विकसित करने और अन्य रेंडरिंग एल्गोरिदम लिखने के लिए है।
टॉम

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

2

यहाँ अनुकूलन सलाह का मेरा दूसरा टुकड़ा है। मेरी पहली सलाह के साथ यह सामान्य उद्देश्य है, न कि भाषा या प्रोसेसर विशिष्ट।

कंपाइलर मैनुअल को अच्छी तरह से पढ़ें और समझें कि यह आपको क्या बता रहा है। कंपाइलर का उपयोग इसके अत्यंत करने के लिए करें।

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

हाँ, संकलक लेखक कोडिंग दिग्गजों की दौड़ से नहीं हैं और संकलक में गलतियाँ होती हैं और मैनुअल के अनुसार और संकलक के सिद्धांत के अनुसार, कभी-कभी चीजों को धीमा करने के लिए तेजी से चीजें बनाते हैं। इसलिए आपको एक बार में एक कदम उठाना होगा और पहले और बाद के प्रदर्शन को मापना होगा।

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

सादर

निशान

जब मैं पहली बार कोड का एक टुकड़ा उठाता हूं तो मुझे आमतौर पर 1.4 - 2.0 गुना अधिक प्रदर्शन का एक कारक मिल सकता है (यानी कोड का नया संस्करण पुराने संस्करण के समय के 1 / 1.4 या 1/2 भाग में चलता है) कंपाइलर के झंडे के साथ दिन या दो बार। दी गई, यह उन वैज्ञानिकों के बीच संकलक की कमी के बारे में टिप्पणी हो सकती है, जो मेरी उत्कृष्टता के लक्षण के बजाय, मेरे द्वारा काम किए जाने वाले कोड की बहुत अधिक उत्पत्ति करते हैं। संकलक झंडे को अधिकतम पर सेट करना (और यह शायद ही -O3 है) 1.05 या 1.1 का एक और कारक प्राप्त करने में महीनों की मेहनत लग सकती है


2

जब डीईसी अपने अल्फा प्रोसेसर के साथ बाहर आया, तो 7 के तहत एक फ़ंक्शन के लिए तर्कों की संख्या को रखने की सिफारिश की गई थी, क्योंकि कंपाइलर हमेशा 6 तर्कों को स्वचालित रूप से रजिस्टरों में डालने की कोशिश करेगा।


x86-64 बिट भी बहुत सारे रजिस्टर-पास मापदंडों की अनुमति देता है, जो फ़ंक्शन कॉल ओवरहेड पर एक नाटकीय प्रभाव डाल सकता है।
टॉम

1

प्रदर्शन के लिए, पहले मुख्य कोड लिखने पर ध्यान दें - घटक, शिथिल युग्मित, आदि, इसलिए जब आपको फिर से लिखना, अनुकूलन या बस प्रोफ़ाइल के लिए एक हिस्से को अलग करना होगा, तो आप इसे बहुत प्रयास किए बिना कर सकते हैं।

ऑप्टिमाइज़र आपके कार्यक्रम के प्रदर्शन को मामूली रूप से मदद करेगा।


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

1

आपको यहां अच्छे उत्तर मिल रहे हैं, लेकिन वे मानते हैं कि आपका कार्यक्रम शुरू करने के लिए इष्टतम के बहुत करीब है, और आप कहते हैं

मान लें कि कार्यक्रम सही ढंग से लिखा गया है, पूर्ण अनुकूलन के साथ संकलित, परीक्षण और उत्पादन में डाल दिया गया है।

मेरे अनुभव में, एक कार्यक्रम सही ढंग से लिखा जा सकता है, लेकिन इसका मतलब यह नहीं है कि यह इष्टतम के पास है। उस बिंदु तक पहुंचने में अतिरिक्त मेहनत लगती है।

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

उसके बाद किया जाता है, माइक्रो-ऑप्टिमाइज़ेशन (हॉट-स्पॉट का) आपको एक अच्छा भुगतान दे सकता है।


1

मैं इंटेल संकलक का उपयोग करता हूं। विंडोज और लिनक्स दोनों पर।

जब कम या ज्यादा मैंने कोड को प्रोफाइल किया। फिर हॉटस्पॉट पर लटकाएं और संकलक को बेहतर काम करने की अनुमति देने के लिए कोड को बदलने की कोशिश कर रहा है।

अगर एक कोड एक कम्प्यूटेशनल है और इसमें बहुत सारे लूप शामिल हैं - इंटेल कंपाइलर में वेक्टराइजेशन रिपोर्ट बहुत मददगार है - मदद में 'vec-report' देखें।

इसलिए मुख्य विचार - प्रदर्शन महत्वपूर्ण कोड पॉलिश करें। बाकी के रूप में - प्राथमिकता सही और बनाए रखने के लिए - लघु कार्य, स्पष्ट कोड जिसे 1 साल बाद समझा जा सकता है।


आप इस प्रश्न का उत्तर देने के करीब पहुंच रहे हैं ..... आप कोड को किस प्रकार का करते हैं, जिससे कंपाइलर के लिए उन प्रकार के अनुकूलन करना संभव हो सके?
EvilTeach

1
सी-स्टाइल (बनाम सी ++) में अधिक लिखने की कोशिश कर रहा है। उदाहरण के लिए वर्चुअल फ़ंक्शंस w / o परम आवश्यकता से परहेज करना, खासकर अगर उन्हें अक्सर बुलाया जाना चाहिए, AddRefs से बचें .. और सभी शांत सामान (फिर से जब तक यह वास्तव में ज़रूरत न हो)। इनलाइनिंग के लिए कोड आसान लिखें - कम पैरामीटर, कम "यदि" -s। जब तक पूर्ण आवश्यकता न हो, वैश्विक चर का उपयोग न करें। डेटा संरचना में - पहले व्यापक फ़ील्ड्स डालें (डबल, int64 इंट से पहले चला जाता है) - इसलिए कंपाइलर ने पहले फ़ील्ड प्राकृतिक आकार पर संरचना को संरेखित किया - पूर्ण के लिए अच्छा संरेखित।
जेएफ

1
डेटा लेआउट और पहुंच प्रदर्शन के लिए बिल्कुल महत्वपूर्ण हैं। प्रोफाइलिंग के बाद - मैं कभी-कभी पहुंच की स्थानीयता के बाद कई लोगों में एक संरचना को तोड़ता हूं। एक और सामान्य चाल - int या size-t बनाम char का उपयोग करें - यहां तक ​​कि डेटा मान भी छोटे हैं - विभिन्न पूर्ण से बचें। आंशिक लोडिंग स्टालों के साथ, ब्लॉकिंग को लोड करने के लिए पेनल्टी स्टोर करती है। बेशक जब यह इस तरह के डेटा के बड़े सरणियों की जरूरत पर लागू नहीं होता है।
जेएफ

एक और - सिस्टम कॉल से बचें, जब तक कि कोई वास्तविक आवश्यकता न हो :) - वे बहुत महंगे हैं
जेएफ।

2
@jf: मैंने आपके उत्तर पर +1 किया, लेकिन क्या आप टिप्पणियों का उत्तर शरीर से उत्तर देने के लिए ले सकते हैं? इसे पढ़ना आसान होगा।
क्रिश

1

C ++ में मैंने जिस एक ऑप्टिमाइज़ेशन का इस्तेमाल किया है, वह एक कंस्ट्रक्टर बना रहा है जो कुछ भी नहीं करता है। ऑब्जेक्ट को कार्यशील स्थिति में रखने के लिए मैन्युअल रूप से एक init () को कॉल करना होगा।

इससे उस स्थिति में लाभ होता है जहां मुझे इन वर्गों के बड़े वेक्टर की आवश्यकता होती है।

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

जैसा कि मैं वेक्टर को भरने के लिए ऑब्जेक्ट उत्पन्न करता हूं, मैं उन्हें init () का उपयोग करके सेट करता हूं। यह मेरे कुल पृष्ठ दोषों को सीमित करता है, और इसे भरने के दौरान वेक्टर को आकार देने () की आवश्यकता से बचा जाता है।


6
मेरा मानना ​​है कि एसटीडी का एक सामान्य कार्यान्वयन :: वेक्टर वास्तव में अधिक वस्तुओं का निर्माण नहीं करता है जब आप अधिक क्षमता रखते हैं। यह सिर्फ पेज आवंटित करता है। कंस्ट्रक्टरों को बाद में बुलाया जाता है, प्लेसमेंट नए का उपयोग करते हुए, जब आप वास्तव में वेक्टर को ऑब्जेक्ट जोड़ते हैं - जो कि (संभवतः) इससे पहले कि आप init () कहते हैं, तो आपको वास्तव में अलग इनिट () फ़ंक्शन की आवश्यकता नहीं है। यह भी याद रखें कि भले ही आपका कंस्ट्रक्टर स्रोत कोड में "खाली" हो, पर संकलित कंस्ट्रक्टर में वर्चुअल टेबल और आरटीटीआई जैसी चीजों को इनिशियलाइज़ करने के लिए कोड हो सकता है, इसलिए पेज वैसे भी कंस्ट्रक्शन के समय टच हो जाते हैं।
वायज़र्ड

1
हां। हमारे मामले में हम वेक्टर को पॉप्युलेट करने के लिए push_back का उपयोग करते हैं। वस्तुओं में कोई आभासी कार्य नहीं है, इसलिए यह कोई समस्या नहीं है। पहली बार हमने इसे कंस्ट्रक्टर के साथ आज़माया, पृष्ठ दोषों की मात्रा से हम चकित थे। मुझे एहसास हुआ कि क्या हुआ था, और हमने कंस्ट्रक्टर की हिम्मत को कम कर दिया, और पृष्ठ दोष समस्या गायब हो गई।
EvilTeach

बल्कि मुझे आश्चर्य। क्या आप सी + + और एसटीएल कार्यान्वयन का उपयोग कर रहे थे?
डेविड थॉर्नले

3
मैं दूसरों से सहमत हूं, यह एसटीडी के खराब कार्यान्वयन की तरह लगता है :: वेक्टर। यहां तक ​​कि अगर आपकी वस्तुओं में vtables होते हैं, तो भी उनका निर्माण आपके पुश_बैक तक नहीं किया जाएगा। आपको डिफ़ॉल्ट कंस्ट्रक्टर को निजी घोषित करके इसका परीक्षण करने में सक्षम होना चाहिए, क्योंकि सभी वेक्टर को पुश-बैक के लिए कॉपी-कंस्ट्रक्टर की आवश्यकता होगी।
टॉम

1
@ दाविद - कार्यान्वयन AIX पर था।
ईविलटच

1

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

पिछली बार जब मुझे वास्तव में समग्र प्रदर्शन में सुधार करना था, तो मैंने उप-अपनाने वाले एल्गोरिदम के लिए नजर रखी और उन जगहों की तलाश की, जिनमें कैश की समस्या होने की संभावना थी। मैंने पहले प्रदर्शन को मापा और मापा, और फिर प्रत्येक परिवर्तन के बाद। फिर कंपनी ढह गई, लेकिन यह दिलचस्प और शिक्षाप्रद काम था।


0

मुझे लंबे समय से संदेह है, लेकिन कभी भी यह साबित नहीं किया गया कि सरणियों की घोषणा करना ताकि वे 2 की शक्ति रखते हैं, तत्वों की संख्या के रूप में, आशावादी को कई बिट्स द्वारा शिफ्ट द्वारा एक गुणा की जगह एक गुणा करके ताकत में कमी करने में सक्षम बनाता है। व्यक्तिगत तत्व।


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

+1 निल्स, और इसका एक विशिष्ट घटना इंटेल हार्डवेयर पर "64k अलियासिंग" है।
टॉम

यह एक ऐसी चीज है जो आसानी से डिसएफ़ीड को देखकर, जिस तरह से होती है, आसानी से नापसंद हो जाती है। मैं चकित था, सालों पहले, यह देखते हुए कि gcc कैसे शिफ्ट्स और ऐड्स के साथ सभी प्रकार के निरंतर गुणाओं को अनुकूलित करेगा। जैसे val * 7क्या अन्यथा में बदल जाएगा (val << 3) - val
डैश-टॉम-बैंग

0

स्रोत फ़ाइल के शीर्ष पर छोटे और / या अक्सर कहा जाने वाला फ़ंक्शन रखें। यह कंपाइलर के लिए इनलाइनिंग के अवसरों को खोजना आसान बनाता है।


वास्तव में? क्या आप इसके लिए एक तर्क और उदाहरण का हवाला दे सकते हैं? यह नहीं कह रहा है कि यह असत्य है, बस यह स्पष्ट नहीं है कि स्थान मायने रखेगा।
अंडरस्कोर_ड

@underscore_d फ़ंक्शन परिभाषा को ज्ञात होने तक यह कुछ इनलाइन नहीं कर सकता है। हालांकि आधुनिक संकलक कई पास बना सकते हैं ताकि परिभाषा कोड पीढ़ी के समय में ज्ञात हो, मैं इसे नहीं मानता।
मार्क रैनसम

मुझे लगता है कि कंपाइलर्स फिजिकल फंक्शन ऑर्डर के बजाय एब्सट्रैक्ट कॉल ग्राफ्स को काम करते हैं, जिसका मतलब यह नहीं है। यकीन है, मुझे लगता है कि यह अतिरिक्त सावधानी बरतने के लिए चोट नहीं करता है - खासकर जब, एक तरफ प्रदर्शन, आईएमओ यह सिर्फ उन कार्यों को परिभाषित करने के लिए अधिक तर्कसंगत लगता है जो उन्हें कॉल करने से पहले कहते हैं। मुझे प्रदर्शन का परीक्षण करना होगा, लेकिन अगर यह मायने रखता है तो आश्चर्य होगा, लेकिन तब तक, मैं आश्चर्यचकित हूं!
अंडरस्कोर_ड
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.