जेवीएम टेल-कॉल ऑप्टिमाइज़ेशन पर क्या सीमाएँ लगाता है


36

क्लोजर अपने आप ही टेल कॉल ऑप्टिमाइजेशन नहीं करता है: जब आपके पास टेल रीसर्चिव फंक्शन होता है और आप इसे ऑप्टिमाइज़ करना चाहते हैं, तो आपको स्पेशल फॉर्म का इस्तेमाल करना होगा recur। इसी तरह, यदि आपके पास दो पारस्परिक रूप से पुनरावर्ती कार्य हैं, तो आप केवल उपयोग करके उन्हें अनुकूलित कर सकते हैं trampoline

स्काला संकलक एक पुनरावर्ती कार्य के लिए TCO प्रदर्शन करने में सक्षम है, लेकिन दो परस्पर पुनरावर्ती कार्यों के लिए नहीं।

जब भी मैंने इन सीमाओं के बारे में पढ़ा है, तो वे हमेशा JVM मॉडल के लिए कुछ सीमा से जुड़े थे। मैं संकलक के बारे में बहुत कुछ नहीं जानता, लेकिन यह मुझे थोड़ा पहेली करता है। से उदाहरण लेता हूं Programming Scala। यहाँ समारोह

def approximate(guess: Double): Double =
  if (isGoodEnough(guess)) guess
  else approximate(improve(guess))

में अनुवादित है

0: aload_0
1: astore_3
2: aload_0
3: dload_1
4: invokevirtual #24; //Method isGoodEnough:(D)Z
7: ifeq
10: dload_1
11: dreturn
12: aload_0
13: dload_1
14: invokevirtual #27; //Method improve:(D)D
17: dstore_1
18: goto 2

इसलिए, बायटेकोड स्तर पर, बस जरूरत है goto। इस मामले में, वास्तव में, कंपाइलर द्वारा कड़ी मेहनत की जाती है।

अंतर्निहित वर्चुअल मशीन की क्या सुविधा संकलक को TCO को अधिक आसानी से संभालने की अनुमति देगी?

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

जवाबों:


25

अब, मैं क्लोजर के बारे में ज्यादा नहीं जानता हूं और स्काला के बारे में थोड़ा जानता हूं, लेकिन मैं इसे एक शॉट दूंगा।

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

function forward(obj: Callable<int, int>, arg: int) =
    let arg1 <- arg + 1 in obj.call(arg1)

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

अंतर्निहित वर्चुअल मशीन की क्या सुविधा संकलक को TCO को अधिक आसानी से संभालने की अनुमति देगी?

एक टेल कॉल निर्देश, जैसे कि Lua 5.1 VM में। आपका उदाहरण ज्यादा सरल नहीं है। मेरा कुछ इस तरह से हो जाता है:

push arg
push 1
add
load obj
tailcall Callable.call
// implicit return; stack frame was recycled

एक विचार के रूप में, मुझे उम्मीद नहीं होगी कि वास्तविक मशीनें JVM की तुलना में अधिक स्मार्ट होंगी।

आप सही हैं, वे नहीं हैं। वास्तव में, वे कम स्मार्ट होते हैं और इस प्रकार स्टैक फ्रेम जैसी चीजों के बारे में भी नहीं जानते (बहुत)। यही कारण है कि स्टैक स्पेस को री-यूज़ करने और रिटर्न एड्रेस को पुश किए बिना कोड को जंप करने जैसी ट्रिक्स खींच सकते हैं।


समझा। मुझे एहसास नहीं था कि कम स्मार्ट होने से एक अनुकूलन की अनुमति मिल सकती है जो अन्यथा निषिद्ध होगी।
एंड्रिया

7
+1, tailcallजेवीएम के लिए निर्देश पहले से ही 2007 के रूप में प्रस्तावित किया गया है: Wayback मशीन के माध्यम से sun.com पर ब्लॉग । ओरेकल टेकओवर के बाद, यह लिंक 404 का है। मुझे लगता है कि यह JVM 7 प्राथमिकता सूची में नहीं बना था।
सफ़्फ़ जूल 21'12

1
एक tailcallनिर्देश केवल पूंछ कॉल के रूप में एक पूंछ कॉल को चिह्नित करेगा। क्या JVM तब वास्तव में अनुकूलित टेल कॉल एक पूरी तरह से अलग सवाल है। CLI CIL में एक .tailनिर्देश उपसर्ग है, फिर भी लंबे समय तक Microsoft 64-बिट CLR इसे ऑप्टिमाइज़ नहीं करता था। OTOH, आईबीएम J9 JVM करता पूंछ कॉल पता लगाने और उन्हें अनुकूलित करता है, यह बताने के लिए जो कॉल पूंछ कॉल कर रहे हैं एक विशेष निर्देश की जरूरत के बिना। पूंछ कॉल की व्याख्या करना और पूंछ कॉल का अनुकूलन वास्तव में ऑर्थोगोनल है। (इस तथ्य के अलावा कि सांख्यिकीय रूप से समर्पण कौन सी कॉल टेल कॉल है या अयोग्य नहीं हो सकता है। डननो।)
जर्ग डब्ल्यू मित्तग

@ JörgWMittag आप एक अच्छा बिंदु बनाते हैं, एक JVM आसानी से पैटर्न का पता लगा सकता है call something; oreturn। एक JVM कल्पना अद्यतन के प्राथमिक काम एक स्पष्ट पूंछ-कॉल अनुदेश लागू करने के लिए नहीं है, लेकिन करने के लिए किया जाएगा जनादेश कि इस तरह के एक निर्देश अनुकूलित है। ऐसा निर्देश केवल कंपाइलर लेखकों की नौकरी को आसान बनाता है: जेवीएम लेखक को मान्यता से परे मंगवाने से पहले उस अनुदेश अनुक्रम को पहचानना सुनिश्चित नहीं करना पड़ता है, और एक्स-> बायोटेक कंपाइलर यह आश्वासन दे सकते हैं कि उनका बायोटेक या तो अमान्य है या नहीं वास्तव में अनुकूलित, कभी सही-लेकिन-ढेर-अतिप्रवाह।

@ डायलेन: अनुक्रम call something; return;केवल एक टेल कॉल के बराबर होगा यदि उस चीज़ को कहा जाता है जो स्टैक ट्रेस के लिए कभी नहीं पूछता है; यदि प्रश्न में विधि आभासी है या आभासी विधि कहलाती है, तो JVM को यह जानने का कोई तरीका नहीं होगा कि क्या वह स्टैक के बारे में पूछताछ कर सकती है।
सुपरकैट

12

क्लोजर लूप में पूंछ पुनरावृत्ति का स्वत: अनुकूलन कर सकता है : जेवीएम पर ऐसा करना निश्चित रूप से संभव है क्योंकि स्कैब साबित होता है।

यह वास्तव में ऐसा नहीं करने के लिए एक डिज़ाइन निर्णय था - recurयदि आप इस सुविधा को चाहते हैं तो आपको विशेष रूप से स्पष्ट रूप से उपयोग करना होगा। मेल थ्रेड देखें : क्यों कोई टेल कॉल क्लोजर गूगल ग्रुप पर ऑप्टिमाइज़ेशन नहीं करता है।

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

जेवीएम के भविष्य के पुनरावृत्ति को यह क्षमता प्राप्त होने की संभावना है, हालांकि संभवतः एक विकल्प के रूप में ताकि पुराने कोड के लिए पीछे की ओर संगत व्यवहार बनाए रखा जाए। कहो, Geeknizer में विशेषताएँ पूर्वावलोकन जावा 9 के लिए इसे सूचीबद्ध करता है:

पूंछ कॉल और निरंतरता जोड़ना ...

बेशक, भविष्य के रोडमैप हमेशा परिवर्तन के अधीन होते हैं।

जैसा कि यह पता चला है, यह उतना बड़ा सौदा नहीं है। क्लोजर कोडिंग के 2 वर्षों में, मैं कभी भी ऐसी स्थिति में नहीं चला हूं जहां TCO की कमी एक मुद्दा था। इसके मुख्य कारण हैं:

  • आप पहले से ही 99% आम मामलों के लिए recurया लूप के साथ तेजी से पूंछ पुनरावृत्ति प्राप्त कर सकते हैं । सामान्य पूंछ पुनरावृत्ति का मामला सामान्य कोड में बहुत दुर्लभ है
  • यहां तक ​​कि जब आपको पारस्परिक पुनरावृत्ति की आवश्यकता होती है, तो अक्सर पुनरावृत्ति की गहराई उथली होती है, ताकि आप इसे बिना TCO के वैसे भी स्टैक पर कर सकें। TCO सब के बाद एक "अनुकूलन" है ...।
  • बहुत दुर्लभ मामलों में जहां आपको गैर-स्टैक-खपत वाले पारस्परिक पुनरावृत्ति के कुछ रूप की आवश्यकता होती है, बहुत सारे अन्य विकल्प हैं जो एक ही लक्ष्य प्राप्त कर सकते हैं: आलसी क्रम, ट्रैम्पोलाइन आदि।

"भविष्य का पुनरावृत्ति" - Geeknizer में विशेषताएँ पूर्वावलोकन जावा 9 के लिए कहता है: पूंछ कॉल और निरंतरता जोड़ना - क्या यह है?
कुटकी

1
हां - यह बात है। बेशक, भविष्य के रोडमैप हमेशा बदलाव के अधीन होते हैं ....
मिकेरा

5

एक विचार के रूप में, मुझे उम्मीद नहीं होगी कि वास्तविक मशीनें JVM की तुलना में अधिक स्मार्ट होंगी।

यह चालाक होने के बारे में नहीं है, बल्कि अलग होने के बारे में है। कुछ समय पहले तक, JVM को एकल भाषा (जावा, जाहिर है) के लिए विशेष रूप से डिजाइन और अनुकूलित किया गया था, जिसमें बहुत सख्त मेमोरी और कॉलिंग मॉडल हैं।

इतना ही नहीं कोई भी gotoसंकेत या संकेत नहीं था, यहां तक ​​कि 'नंगे' फ़ंक्शन को कॉल करने का भी कोई तरीका नहीं था (वह जो एक वर्ग के भीतर परिभाषित विधि नहीं थी)।

वैचारिक रूप से, JVM को लक्षित करते समय, एक संकलक लेखक को यह पूछना होगा कि "मैं जावा की शर्तों में इस अवधारणा को कैसे व्यक्त कर सकता हूं?"। और जाहिर है, जावा में टीसीओ को व्यक्त करने का कोई तरीका नहीं है।

ध्यान दें कि इन्हें JVM की विफलताओं के रूप में नहीं देखा जाता है, क्योंकि ये जावा के लिए आवश्यक नहीं हैं। जैसे ही Java को इन जैसे कुछ फीचर की आवश्यकता होती है, इसे JVM में जोड़ दिया जाता है।

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


3

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

क्या आपने देखा है कि विधि का पता 0 से शुरू होता है? यही कारण है कि सभी तरीकों ofsets 0 से आरंभ? JVM एक विधि के बाहर कूदने की अनुमति नहीं देता है।

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

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


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