"जेवीएम टेल-कॉल ऑप्टिमाइज़ेशन का समर्थन नहीं करता है, इसलिए मैं बहुत सारे विस्फोट के पूर्वानुमान की भविष्यवाणी करता हूं"
जो कोई भी यह कहता है (1) पूंछ-कॉल अनुकूलन को नहीं समझता है, या (2) जेवीएम, या (3) दोनों को नहीं समझता है।
मैं विकिपीडिया से टेल-कॉल की परिभाषा के साथ शुरू करूँगा (यदि आपको विकिपीडिया पसंद नहीं है, तो यहां एक विकल्प है ):
कंप्यूटर विज्ञान में, एक टेल कॉल एक सबरूटीन कॉल है जो कि अंतिम प्रक्रिया के रूप में एक और प्रक्रिया के अंदर होती है; यह एक रिटर्न वैल्यू का उत्पादन कर सकता है जो फिर कॉलिंग प्रक्रिया द्वारा तुरंत लौटाया जाता है
नीचे दिए गए कोड में, कॉल bar()
टेल कॉल टू foo()
:
private void foo() {
// do something
bar()
}
टेल कॉल ऑप्टिमाइज़ेशन तब होता है जब भाषा कार्यान्वयन, एक टेल कॉल को देखकर, सामान्य विधि आह्वान (जो स्टैक फ्रेम बनाता है) का उपयोग नहीं करता है, लेकिन इसके बजाय एक शाखा बनाता है। यह एक अनुकूलन है क्योंकि स्टैक फ्रेम को मेमोरी की आवश्यकता होती है, और इसके लिए फ्रेम पर जानकारी (जैसे रिटर्न एड्रेस) को पुश करने के लिए सीपीयू साइकिल की आवश्यकता होती है, और क्योंकि कॉल / रिटर्न जोड़ी को बिना शर्त कूद की तुलना में अधिक सीपीयू चक्र की आवश्यकता होती है।
TCO को अक्सर रिकर्सन पर लागू किया जाता है, लेकिन यह इसका एकमात्र उपयोग नहीं है। और न ही यह सभी पुनरावर्तन पर लागू होता है। उदाहरण के लिए, गणना करने के लिए सरल पुनरावर्ती कोड, टेल-कॉल को अनुकूलित नहीं किया जा सकता है, क्योंकि फ़ंक्शन में होने वाली आखिरी चीज एक गुणन ऑपरेशन है।
public static int fact(int n) {
if (n <= 1) return 1;
else return n * fact(n - 1);
}
टेल कॉल ऑप्टिमाइज़ेशन को लागू करने के लिए, आपको दो चीजों की आवश्यकता है:
- एक प्लेटफॉर्म जो सबट्रूटीन कॉल के अलावा ब्रांचिंग का समर्थन करता है।
- एक स्थिर विश्लेषक जो यह निर्धारित कर सकता है कि क्या पूंछ कॉल अनुकूलन संभव है।
बस। जैसा कि मैंने कहीं और उल्लेख किया है, जेवीएम (किसी भी अन्य ट्यूरिंग-पूर्ण वास्तुकला की तरह) के पास एक गोटो है। यह एक बिना शर्त गोटो होता है , लेकिन एक सशर्त शाखा का उपयोग करके कार्यक्षमता को आसानी से लागू किया जा सकता है।
स्थैतिक विश्लेषण टुकड़ा क्या मुश्किल है। एक ही कार्य के भीतर, यह कोई समस्या नहीं है। उदाहरण के लिए, यहां मानों को योग करने के लिए एक पूंछ-पुनरावर्ती स्काला फ़ंक्शन है List
:
def sum(acc:Int, list:List[Int]) : Int = {
if (list.isEmpty) acc
else sum(acc + list.head, list.tail)
}
यह कार्य निम्नलिखित बायोटेक में बदल जाता है:
public int sum(int, scala.collection.immutable.List);
Code:
0: aload_2
1: invokevirtual #63; //Method scala/collection/immutable/List.isEmpty:()Z
4: ifeq 9
7: iload_1
8: ireturn
9: iload_1
10: aload_2
11: invokevirtual #67; //Method scala/collection/immutable/List.head:()Ljava/lang/Object;
14: invokestatic #73; //Method scala/runtime/BoxesRunTime.unboxToInt:(Ljava/lang/Object;)I
17: iadd
18: aload_2
19: invokevirtual #76; //Method scala/collection/immutable/List.tail:()Ljava/lang/Object;
22: checkcast #59; //class scala/collection/immutable/List
25: astore_2
26: istore_1
27: goto 0
नोट goto 0
अंत में। तुलना करके, एक बराबर जावा फ़ंक्शन (जिसे Iterator
स्कैला सूची को सिर और पूंछ में तोड़ने के व्यवहार की नकल करने के लिए उपयोग करना चाहिए ) निम्नलिखित बायोटेक में बदल जाता है। ध्यान दें कि पिछले दो ऑपरेशन अब एक आह्वान हैं , जिसके बाद उस पुनरावर्ती मंगलाचरण द्वारा उत्पादित मूल्य का एक स्पष्ट वापसी होती है।
public static int sum(int, java.util.Iterator);
Code:
0: aload_1
1: invokeinterface #64, 1; //InterfaceMethod java/util/Iterator.hasNext:()Z
6: ifne 11
9: iload_0
10: ireturn
11: iload_0
12: aload_1
13: invokeinterface #70, 1; //InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
18: checkcast #25; //class java/lang/Integer
21: invokevirtual #74; //Method java/lang/Integer.intValue:()I
24: iadd
25: aload_1
26: invokestatic #43; //Method sum:(ILjava/util/Iterator;)I
29: ireturn
एकल फ़ंक्शन का टेल कॉल ऑप्टिमाइज़ेशन तुच्छ है: कंपाइलर देख सकता है कि कोई कोड नहीं है जो कॉल के परिणाम का उपयोग करता है, इसलिए यह इनवोक को ए के साथ बदल सकता है goto
।
जहां जीवन मुश्किल हो जाता है अगर आपके पास कई तरीके हैं। जेवीएम की शाखाओं के निर्देश, सामान्य उद्देश्य प्रोसेसर जैसे कि 80x86 के विपरीत, एक एकल विधि तक ही सीमित हैं। यदि आपके पास निजी विधियां हैं तो यह अभी भी अपेक्षाकृत सरल है: कंपाइलर उन विधियों को उपयुक्त रूप से इनलाइन करने के लिए स्वतंत्र है, इसलिए टेल कॉल को अनुकूलित कर सकते हैं (यदि आप सोच रहे हैं कि यह कैसे काम कर सकता है, तो एक सामान्य विधि पर विचार करें जो switch
नियंत्रण व्यवहार का उपयोग करता है )। आप इस तकनीक को एक ही वर्ग में कई सार्वजनिक तरीकों से भी बढ़ा सकते हैं: संकलक विधि निकायों को इनलाइन करता है, सार्वजनिक पुल के तरीके प्रदान करता है, और आंतरिक कॉल कूदता है।
लेकिन, यह मॉडल तब टूट जाता है जब आप विभिन्न वर्गों में सार्वजनिक तरीकों पर विचार करते हैं, विशेष रूप से इंटरफेस और क्लास लोडर के प्रकाश में। स्रोत-स्तरीय संकलक के पास टेल-कॉल अनुकूलन को लागू करने के लिए पर्याप्त ज्ञान नहीं है। हालांकि, "नंगे-धातु" कार्यान्वयन के विपरीत , * जेवीएम (हॉटस्पॉट कंपाइलर के रूप में (कम से कम, एक्स-सन कंपाइलर करता है) के रूप में ऐसा करने के लिए जानकारी है। पूंछ-कॉल अनुकूलन, और संदेह नहीं है, लेकिन यह हो सकता है ।
जो मुझे आपके प्रश्न के दूसरे भाग में लाता है, जिसे मैं "हमें ध्यान रखना चाहिए?"
स्पष्ट रूप से, यदि आपकी भाषा पुनरावृत्ति को पुनरावृत्ति के लिए अपने एकमात्र आदिम के रूप में उपयोग करती है, तो आप परवाह करते हैं। लेकिन, जिन भाषाओं को इस सुविधा की आवश्यकता है, वे इसे लागू कर सकते हैं; एकमात्र मुद्दा यह है कि क्या उक्त भाषा के लिए एक कंपाइलर एक वर्ग का उत्पादन कर सकता है जिसे एक जावा वर्ग द्वारा मनमाना कॉल किया जा सकता है।
उस मामले के बाहर, मैं यह कहकर अधखुला आमंत्रित करने जा रहा हूं कि यह अप्रासंगिक है। अधिकांश पुनरावर्ती कोड जो मैंने देखे हैं (और मैंने बहुत सारे ग्राफ प्रोजेक्ट्स के साथ काम किया है) टेल-कॉल अनुकूलन योग्य नहीं है । साधारण तथ्य की तरह, यह राज्य बनाने के लिए पुनरावृत्ति का उपयोग करता है, और पूंछ संचालन संयोजन है।
कोड के लिए जो टेल-कॉल अनुकूलन योग्य है, उस कोड को अनुवाद करने योग्य रूप में अनुवाद करना अक्सर सरल होता है। उदाहरण के लिए, उस sum()
फ़ंक्शन को जो मैंने पहले दिखाया था, को सामान्यीकृत किया जा सकता है foldLeft()
। यदि आप स्रोत को देखते हैं, तो आप देखेंगे कि यह वास्तव में पुनरावृत्त ऑपरेशन के रूप में लागू किया गया है। Jörg W Mittag में फ़ंक्शन कॉल के माध्यम से कार्यान्वित एक राज्य मशीन का एक उदाहरण था; बहुत सारे कुशल (और बनाए रखने योग्य) राज्य मशीन कार्यान्वयन हैं जो फ़ंक्शन कॉल पर भरोसा नहीं करते हैं जो जंप में अनुवादित हो रहे हैं।
मैं पूरी तरह से अलग कुछ के साथ खत्म करेंगे। यदि आप SICP में फुटनोट्स से अपना रास्ता गुगल करते हैं, तो आप यहाँ समाप्त हो सकते हैं । मुझे व्यक्तिगत रूप से लगता है कि मेरे संकलक JSR
द्वारा प्रतिस्थापित करने की तुलना में बहुत अधिक दिलचस्प जगह है JUMP
।