performance - एक सामान्य अनुकूलन संदर्भ द्वारा पास संरचना क्यों नहीं है?




optimization compiler-construction (8)

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

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

#include "iostream.h"

struct S {
    int i, j, k, l, m, n, o, p;
};

int foo(S s) {
    return s.i + s.j + s.k + s.l + s.m + s.n + s.o + s.p;
}

int main() {
    S s;
    int bar = foo(s);
    cout << bar;
}

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

नोट: कंपाइलर स्विच का उपयोग किया जाता है: जीसीसी-ओ 2 (-ओ 3 इनलाइन फू ()।), डीएमडी-ओ -इनलाइन- कृपया।

संपादित करें: जाहिर है, सामान्य मामले में पास-बाय-वैल्यू बनाम पास-बाय-रेफरेंस का अर्थशास्त्र समान नहीं होगा, जैसे कि कॉपी कन्स्ट्रक्टर शामिल हैं या मूल संरचना को कैली में संशोधित किया गया है। हालांकि, वास्तविक दुनिया के कई परिदृश्यों में, अर्थशास्त्र व्यवहार के व्यवहार में समान होगा। ये वे मामले हैं जिनके बारे में मैं पूछ रहा हूं।


कंपाइलर को यह सुनिश्चित करने की आवश्यकता होगी कि पारित की गई संरचना (कॉलिंग कोड में नामित) में संशोधित नहीं है

double x; // using non structs, oh-well

void Foo(double d)
{
      x += d; // ok
      x += d; // Oops
}

void main()
{
     x = 1;
     Foo(x);
}

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

आपके कंपाइलर को पता लगाने की आवश्यकता होगी) कि foo संरचना को संशोधित नहीं करता है; बी) कि foo संरचना तत्वों के भौतिक स्थान पर कोई गणना नहीं करता है; और सी) कि कॉलर, या कॉलर द्वारा उत्पन्न एक और थ्रेड, foo समाप्त होने से पहले संरचना को संशोधित नहीं करता है।

आपके उदाहरण में, यह कल्पना की जा सकती है कि संकलक इन चीजों को कर सकता है - लेकिन सहेजी गई स्मृति अपरिहार्य है और शायद अनुमान लगाने के लायक नहीं है। क्या होता है यदि आप एक ही प्रोग्राम को ऐसे स्ट्रक्चर के साथ चलाते हैं जिसमें दो मिलियन तत्व हैं?


पास-बाय-रेफरेंस पास-बाय-एड्रेस / पॉइंटर के लिए सिंटैक्टिक चीनी है। इसलिए फ़ंक्शन को पैरामीटर के मान को पढ़ने के लिए एक सूचक को निहित रूप से अव्यवस्थित करना चाहिए। पॉइंटर को डिफरेंस करना अधिक महंगा हो सकता है (यदि लूप में) तो कॉपी-बाय-वैल्यू के लिए स्ट्रक्चर कॉपी।

सबसे महत्वपूर्ण बात यह है कि, जैसे कि दूसरों ने उल्लेख किया है, पास-बाय-रेफरेंस पास-बाय-वैल्यू से अलग अर्थशास्त्र है। const संदर्भ का मतलब यह नहीं है कि संदर्भित मान नहीं बदलता है। अन्य फ़ंक्शन कॉल संदर्भित मान को बदल सकते हैं।


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

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

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


मूल्य से गुजरने के कई कारण हैं, और कंपाइलर आपके इरादे को अनुकूलित करने के लिए अपना कोड तोड़ सकता है।

उदाहरण, यदि बुलाया गया कार्य किसी भी तरह से संरचना को संशोधित करता है। यदि आप का उद्देश्य कॉलर को वापस पास करने का इरादा है तो आप या तो एक सूचक / संदर्भ पास करेंगे या इसे स्वयं वापस कर देंगे।

आप जो संकलक करने के लिए कह रहे हैं वह आपके कोड के व्यवहार को बदलता है, जिसे एक कंपाइलर बग माना जाएगा।

यदि आप अनुकूलन करना चाहते हैं और संदर्भ द्वारा पास करना चाहते हैं तो सभी माध्यमों से संदर्भों को स्वीकार करने के लिए किसी के मौजूदा कार्य / विधि परिभाषाओं को संशोधित करना; यह करना मुश्किल नहीं है। आप इसे महसूस किए बिना टूटने पर आश्चर्यचकित हो सकते हैं।


यह न भूलें कि सी / सी ++ में कंपाइलर को फंक्शन घोषणा पर आधारित फ़ंक्शन को कॉल करने में सक्षम होना चाहिए।

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

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

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

मुझे यकीन नहीं है कि कोई भी करता है (वास्तव में, अगर कोई ऐसा करता है तो मुझे आश्चर्य होगा, क्योंकि शायद इसे अक्सर लागू नहीं किया जा सका)।

बेशक, प्रोग्रामर के रूप में (सी ++ का उपयोग करते समय) आप संकलक को जब भी संभव हो वहां const& पैरामीटर का उपयोग करके इस अनुकूलन को निष्पादित करने के लिए मजबूर कर सकते हैं। मुझे पता है कि आप पूछ रहे हैं कि संकलक स्वचालित रूप से ऐसा क्यों नहीं कर सकता है, लेकिन मुझे लगता है कि यह अगली सबसे अच्छी बात है।


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





struct