क्या और कंपाइलर पुनरावर्ती तर्क को गैर-पुनरावर्ती तर्क में बदल सकते हैं?


15

मैं F # सीख रहा हूं और यह प्रभावित करने लगा है कि जब मैं C # प्रोग्रामिंग कर रहा हूं तो मुझे कैसा लगता है। उस अंत तक, मैं पुनरावृत्ति का उपयोग कर रहा था जब मुझे लगता है कि परिणाम पठनीयता में सुधार करता है और मैं इसे एक ढेर प्रवाह से घुमावदार होने की कल्पना नहीं कर सकता।

यह मुझे यह पूछने की ओर ले जाता है कि संकलक पुनरावर्ती कार्यों को स्वचालित रूप से एक समान गैर-पुनरावर्ती रूप में परिवर्तित कर सकता है या नहीं?


टेल कॉल ऑप्टिमाइज़ेशन एक अच्छा उदाहरण है यदि मूल उदाहरण लेकिन यह केवल तभी काम करता है जब आपके पास return recursecall(args);पुनरावृत्ति के लिए है, एक स्पष्ट स्टैक बनाने और इसे नीचे घुमावदार करने से अधिक जटिल सामान संभव है, लेकिन मुझे संदेह है कि
शाफ़्ट फ्रीक

@ratchet freak: रिकर्सन का मतलब "अभिकलन नहीं है जो स्टैक का उपयोग कर रहा है"।
जियोर्जियो

1
@ जियोर्जियो मुझे पता है, लेकिन एक स्टैक एक पुनरावृत्ति को पाश में बदलने का सबसे आसान तरीका है
शाफ़्ट सनकी

जवाबों:


21

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

int foo(n) {
  ...
  return bar(n);
}

यहां, भाषा यह पहचानने में सक्षम है कि जो परिणाम लौटाया जा रहा है, वह दूसरे फ़ंक्शन का परिणाम है और एक जंप में नए स्टैक फ्रेम के साथ फ़ंक्शन कॉल को बदलें।

एहसास है कि क्लासिक factorial विधि:

int factorial(n) {
  if(n == 0) return 1;
  if(n == 1) return 1;
  return n * factorial(n - 1);
}

है निरीक्षण वापसी पर आवश्यक की वजह से पूंछ कॉल optimizatable।

इस टेल कॉल को अनुकूलन योग्य बनाने के लिए,

int _fact(int n, int acc) {
    if(n == 1) return acc;
    return _fact(n - 1, acc * n);
}

int factorial(int n) {
    if(n == 0) return 1;
    return _fact(n, 1);
}

इस कोड को gcc -O2 -S fact.c(-ओ 2 को कंपाइलर में ऑप्टिमाइज़ेशन को सक्षम करने के लिए आवश्यक है, लेकिन -ओ 3 के अधिक अनुकूलन के साथ इसे पढ़ना मानव के लिए कठिन हो जाता है ...)

_fact:
.LFB0:
        .cfi_startproc
        cmpl    $1, %edi
        movl    %esi, %eax
        je      .L2
        .p2align 4,,10
        .p2align 3
.L4:
        imull   %edi, %eax
        subl    $1, %edi
        cmpl    $1, %edi
        jne     .L4
.L2:
        rep
        ret
        .cfi_endproc

एक सेगमेंट में देख सकता है .L4, jneबजाय एक call(जो एक नए स्टैक फ्रेम के साथ सबरूटीन कॉल करता है)।

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


1
मुझे यकीन नहीं है कि यह वही है जो ओपी पूछ रहा है। सिर्फ इसलिए कि रनटाइम एक निश्चित तरीके से स्टैक स्पेस का उपभोग करता है या नहीं करता है, इसका मतलब यह नहीं है कि फ़ंक्शन पुनरावर्ती नहीं है।

1
@MattFenwick आपका क्या मतलब है? "यह मुझे यह पूछने के लिए प्रेरित करता है कि संकलक स्वचालित रूप से पुनरावर्ती कार्यों को एक समान गैर-पुनरावर्ती रूप में परिवर्तित कर सकता है" - जवाब "कुछ शर्तों के तहत हां" है। शर्तों का प्रदर्शन किया गया है, और कुछ अन्य लोकप्रिय भाषाओं में टेल्का कॉल ऑप्टिमाइज़ेशन के साथ कुछ गैचा हैं, जिनका मैंने उल्लेख किया है।

9

सावधानी से चलना।

जवाब हाँ है, लेकिन हमेशा नहीं, और उन सभी को नहीं। यह एक ऐसी तकनीक है जो कुछ अलग नामों से जाती है, लेकिन आप यहां और विकिपीडिया पर कुछ बहुत ही निश्चित जानकारी पा सकते हैं ।

मैं "टेल कॉल ऑप्टिमाइज़ेशन" नाम पसंद करता हूं, लेकिन अन्य लोग हैं और कुछ लोग इस शब्द को भ्रमित करेंगे।

उन्होंने कहा कि कुछ महत्वपूर्ण बातें हैं:

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

  • C # टेल कॉल को मज़बूती से ऑप्टिमाइज़ नहीं करता है । IL में ऐसा करने का निर्देश है, जो F # संकलक का उत्सर्जन करेगा, लेकिन C # संकलक इसे असंगत रूप से उत्सर्जित करेगा, और JIT स्थिति के आधार पर, JIT यह कर सकती है या नहीं कर सकती है। सभी संकेत हैं कि आपको अपनी पूंछ कॉल पर C # में अनुकूलित होने पर भरोसा नहीं करना चाहिए, ऐसा करने में अतिप्रवाह का जोखिम महत्वपूर्ण और वास्तविक है


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

1
@MattFenwick जो वास्तव में एक शानदार बिंदु है, वास्तविक अर्थों में यह निर्भर करता है, F # कंपाइलर एमिटिंग टेल कॉल निर्देश पूरी तरह से पुनरावर्ती तर्क को बनाए रख रहा है, यह सिर्फ JIT को स्टैक-स्पेस की बजाय स्टैक-स्पेस-रिप्ले फैशन में निष्पादित करने का निर्देश दे रहा है- बढ़ते हुए, हालांकि अन्य संकलक सचमुच एक लूप में संकलित कर सकते हैं। (तकनीकी रूप से जेआईटी एक लूप या संभवतः लूपलेस फैशन को भी संकलित कर रहा है यदि लूप कुल सामने है)
जिमी हॉफ
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.