जैसा कि अन्य कहते हैं, आपको पहले अपने कार्यक्रम के प्रदर्शन को मापना चाहिए, और संभवतः अभ्यास में कोई अंतर नहीं मिलेगा।
फिर भी, एक वैचारिक स्तर से मुझे लगा कि मैं आपके प्रश्न में कुछ चीजें स्पष्ट कर दूंगा। सबसे पहले, आप पूछते हैं:
क्या आधुनिक कॉलरों में फ़ंक्शन कॉल की लागत अभी भी मायने रखती है?
मुख्य शब्द "फ़ंक्शन" और "कंपाइलर" पर ध्यान दें। आपका उद्धरण सबटली अलग है:
याद रखें कि भाषा के आधार पर एक विधि कॉल की लागत महत्वपूर्ण हो सकती है।
यह ऑब्जेक्ट ओरिएंटेड अर्थ में विधियों के बारे में बात कर रहा है।
जब भी "फंक्शन" और "मेथड" को अक्सर परस्पर रूप से उपयोग किया जाता है, तब अंतर होता है जब यह उनकी लागत (जो आप के बारे में पूछ रहे हैं) और जब संकलन की बात आती है (जो आपके द्वारा दिया गया संदर्भ है)।
विशेष रूप से, हमें स्थैतिक प्रेषण बनाम गतिशील प्रेषण के बारे में जानने की आवश्यकता है । मैं इस समय के अनुकूलन को अनदेखा करूँगा।
C जैसी भाषा में, हम आमतौर पर स्थिर प्रेषण के साथ फ़ंक्शन कहते हैं । उदाहरण के लिए:
int foo(int x) {
return x + 1;
}
int bar(int y) {
return foo(y);
}
int main() {
return bar(42);
}
जब कंपाइलर कॉल देखता है foo(y)
, तो यह जानता है कि उस फ़ंक्शन को किस foo
नाम से संदर्भित किया जाता है, इसलिए आउटपुट प्रोग्राम सीधे foo
फ़ंक्शन पर कूद सकता है , जो काफी सस्ता है। यही स्टेटिक प्रेषण का मतलब है।
वैकल्पिक डायनेमिक प्रेषण है , जहां संकलक को पता नहीं है कि किस फ़ंक्शन को कहा जा रहा है। एक उदाहरण के रूप में, यहां कुछ हास्केल कोड (सी समतुल्य गड़बड़ होगा!)।
foo x = x + 1
bar f x = f x
main = print (bar foo 42)
यहां bar
फ़ंक्शन अपने तर्क को बुला रहा है f
, जो कुछ भी हो सकता है। इसलिए कंपाइलर केवल bar
तेज कूदने के निर्देश को संकलित नहीं कर सकता है , क्योंकि यह नहीं जानता कि कहां कूदना है। इसके बजाय, हम जिस कोड के लिए जनरेट करते हैं, वह यह पता लगाने के लिए bar
होगा f
कि यह किस फ़ंक्शन की ओर इशारा कर रहा है, फिर उस पर जाएं। यही डायनेमिक प्रेषण का मतलब है।
उन दोनों उदाहरण कार्यों के लिए हैं । आपने उन विधियों का उल्लेख किया है , जिन्हें गतिशील रूप से भेजे जाने वाले कार्य की एक विशेष शैली के रूप में सोचा जा सकता है। उदाहरण के लिए, यहाँ कुछ पायथन है:
class A:
def __init__(self, x):
self.x = x
def foo(self):
return self.x + 1
def bar(y):
return y.foo()
z = A(42)
bar(z)
y.foo()
कॉल, गतिशील प्रेषण का उपयोग करता है, क्योंकि यह का मान प्राप्त करने के लिए देख रहा है foo
में संपत्ति y
वस्तु, और बुला जो कुछ भी यह पाता है; यह पता नहीं है कि y
कक्षा होगी A
, या कि A
कक्षा में एक foo
विधि शामिल है , इसलिए हम सीधे इसे कूद नहीं सकते हैं।
ठीक है, यह मूल विचार है। ध्यान दें कि स्थिर प्रेषण गतिशील प्रेषण की तुलना में तेजी है , भले ही हम संकलन या व्याख्या है कि क्या की, बाकी सब बराबर। डेरेफ्रेंसिंग दोनों तरह से एक अतिरिक्त लागत लगाता है।
तो यह आधुनिक, अनुकूली संकलनकर्ताओं को कैसे प्रभावित करता है?
ध्यान देने वाली पहली बात यह है कि स्थैतिक प्रेषण को अधिक भारी रूप से अनुकूलित किया जा सकता है: जब हम जानते हैं कि हम किस फ़ंक्शन पर कूद रहे हैं, तो इनलाइनिंग जैसी चीजें कर सकते हैं। डायनामिक डिस्पैच के साथ, हमें नहीं पता कि हम रन टाइम तक जंप कर रहे हैं, इसलिए बहुत अधिक अनुकूलन नहीं है जो हम कर सकते हैं।
दूसरे, यह संभव है कि कुछ भाषाओं में यह अनुमान लगाया जाए कि कुछ डायनेमिक डिस्पैच जंपिंग को समाप्त कर देगा, और इसलिए उन्हें स्टैटिक डिस्पैच में बदल दिया जाएगा। इससे हम अन्य अनुकूलन जैसे कि इनलाइनिंग आदि कर सकते हैं।
उपरोक्त पायथन उदाहरण में ऐसा अनुमान बहुत निराशाजनक है, क्योंकि पायथन अन्य कोड को कक्षाओं और संपत्तियों को ओवरराइड करने की अनुमति देता है, इसलिए यह बहुत मुश्किल है कि सभी मामलों में पकड़ होगी।
यदि हमारी भाषा हमें अधिक प्रतिबंध लगाने की अनुमति देती है, उदाहरण के लिए एक एनोटेशन का उपयोग करके y
कक्षा तक सीमित A
करके, तो हम उस जानकारी का उपयोग लक्ष्य फ़ंक्शन का अनुमान लगाने के लिए कर सकते हैं। उपवर्गों वाली भाषाओं में (जो लगभग सभी भाषाओं की कक्षाएं हैं!) जो वास्तव में पर्याप्त नहीं है, क्योंकि y
वास्तव में एक अलग (उप) वर्ग हो सकता है, इसलिए हमें जावा की final
एनोटेशन जैसी अतिरिक्त जानकारी की आवश्यकता होगी ताकि वास्तव में पता चल सके कि किस फ़ंक्शन को कहा जाएगा।
हास्केल एक OO भाषा नहीं है, लेकिन हम का मूल्य अनुमान लगा सकते हैं f
इनलाइन करने से bar
(जो स्थिर में भेजा) main
, प्रतिस्थापन foo
के लिए y
। चूंकि का लक्ष्य foo
में main
स्थिर जाना जाता है, कॉल स्थिर भेजा हो जाता है, और शायद inlined हो जाएगा और पूरी तरह से दूर अनुकूलित (के बाद से इन कार्यों छोटे हैं, संकलक अधिक उन्हें इनलाइन होने की संभावना है, हालांकि हम चाहते हैं कि सामान्य रूप में पर भरोसा नहीं कर सकते )।
इसलिए लागत नीचे आती है:
- क्या भाषा आपके कॉल को वैधानिक या गतिशील रूप से प्रेषण करती है?
- यदि यह बाद का है, तो क्या भाषा अन्य जानकारी (जैसे प्रकार, वर्ग, एनोटेशन, इनलाइनिंग, आदि) का उपयोग करके लक्ष्य को अनुमान लगाने की अनुमति देती है?
- स्थैतिक रूप से प्रेषण (अनुमान या अन्यथा) को कैसे आक्रामक रूप से अनुकूलित किया जा सकता है?
यदि आप "बहुत गतिशील" भाषा का उपयोग कर रहे हैं, बहुत सारे डायनेमिक प्रेषण के साथ और कुछ गारंटी संकलक के लिए उपलब्ध है, तो हर कॉल एक लागत का भुगतान करेगा। यदि आप "बहुत स्थिर" भाषा का उपयोग कर रहे हैं, तो एक परिपक्व संकलक बहुत तेज़ कोड का उत्पादन करेगा। यदि आप बीच में हैं, तो यह आपकी कोडिंग शैली पर निर्भर करता है और कार्यान्वयन कितना स्मार्ट है।