22

आज तक, मैंने हमेशा सोचा था कि सभ्य कंपाइलर स्वचालित रूप से स्ट्रक्चर पास-बाय-वैल्यू को पास-बाय-रेफरेंस में कनवर्ट करते हैं यदि संरचना इतनी बड़ी है कि बाद वाला तेज़ होगा। मेरे सबसे अच्छे ज्ञान के लिए, यह एक ब्रेनर अनुकूलन की तरह लगता है। हालांकि, यह वास्तव में ऐसा होने के लिए मेरी जिज्ञासा को पूरा करने के लिए, मैंने सी ++ और 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 इनलाइन फू()।), डीएमडी-ओ -इनलाइन- कृपया।

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

उत्तर

22

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

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

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

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

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

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

+0

लिंक-टाइम-ऑप्टिमाइज़ेशन करते समय, a.k.a. लिंक टाइम कोड जनरेशन या पूरे प्रोग्राम संकलन, कंपाइलर को केवल घोषणा के आधार पर कॉल को संकलित करने की आवश्यकता नहीं होती है। इसमें क्या हो रहा है में पूर्ण अंतर्दृष्टि है। एम्बेड किए गए अनुप्रयोगों को संकलित करने के लिए आकार और गति-संवेदनशील, लिंक टाइम कोड पीढ़ी वैसे भी जाने का एकमात्र तरीका है। –

10

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

+2

आप इस तरह के structs के लिए कॉपी-ऑन-राइट सीमेंटिक्स अपनाने सकता है की आवश्यकता होगी। –

+0

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

11

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

यह एक संकलक उत्पन्न निर्णय होने का एक बुरा विचार होगा। कारण यह है कि यह आपके कोड के प्रवाह के बारे में तर्क करना असंभव बनाता है। आप एक कॉल नहीं देख सकते हैं और जानते हैं कि यह वास्तव में क्या करेगा। आपको ए) कोड पता होना चाहिए और बी) संकलक अनुकूलन का अनुमान लगाएं।

4

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

3

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

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

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

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

2

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

1

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

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

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

2

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

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

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

2

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

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

+0

आईआईआरसी कॉन्स टिप्पणी डी 2.0 में मान्य नहीं है। कंपाइलर/कॉन्स संदर्भों के माध्यम से कोई परिवर्तन नहीं करने के लिए स्वतंत्र/मुक्त है। – BCS

1

संकलक सुनिश्चित करें कि struct कि में पारित हो जाता है (के रूप में बुला कोड में नाम) संशोधित नहीं है

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

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

void main() 
{ 
    x = 1; 
    Foo(x); 
} 
संबंधित मुद्दे