2013-05-18 8 views
7

मुझे एक सामान्य लिस्प फ़ंक्शन के प्रदर्शन को समझने में समस्या है (मैं अभी भी एक नौसिखिया हूं)। मेरे पास इस फ़ंक्शन के दो संस्करण हैं, जो किसी दिए गए n तक सभी पूर्णांक के योग की गणना करता है।आम लिस्प: मेरी पूंछ-रिकर्सिव फ़ंक्शन क्यों स्टैक ओवरफ़्लो का कारण बनता है?

गैर पूंछ पुनरावर्ती संस्करण:

(defun addup3 (n) 
    (if (= n 0) 
    0 
    (+ n (addup (- n 1))))) 

पूंछ पुनरावर्ती संस्करण:

(defun addup2 (n) 
    (labels ((f (acc k) 
       (if (= k 0) 
        acc 
        (f (+ acc k) (- k 1))))) 
    (f 0 n))) 

मैं इनपुट n = 1000000 साथ CLISP में इन कार्यों को चलाने के लिए कोशिश कर रहा हूँ। यहाँ परिणाम

[2]> (addup3 1000000) 
500000500000 
[3]> (addup2 1000000) 

*** - Program stack overflow. RESET 

मैं SBCL में दोनों को सफलतापूर्वक चला सकते है, लेकिन गैर-पूंछ पुनरावर्ती एक तेजी से होता है (केवल एक छोटे से से है, लेकिन यह मेरे लिए अजीब लगता है)। मैंने जवाब के लिए Stackoverflow प्रश्नों को खराब कर दिया है लेकिन कुछ ऐसा नहीं मिला। मुझे स्टैक ओवरफ़्लो क्यों मिलता है हालांकि पूंछ-रिकर्सिव फ़ंक्शन को सभी रिकर्सिव फ़ंक्शन कॉल को स्टैक पर नहीं डालने के लिए डिज़ाइन किया गया है? क्या मुझे पूंछ कॉल को अनुकूलित करने के लिए दुभाषिया/कंपाइलर को बताना है? (मैंने (proclaim '(optimize (debug 1)) जैसे कुछ डीबग स्तर सेट करने और ट्रेसिंग क्षमताओं की लागत पर अनुकूलित करने के लिए कुछ पढ़ा है, लेकिन मुझे नहीं पता कि यह क्या करता है)। शायद उत्तर स्पष्ट है और कोड बकवास है, लेकिन मैं इसे समझ नहीं सकता। सहायता की सराहना की जाती है।

संपादित करें: डैनलेई ने टाइपो को इंगित किया, यह पहले समारोह में addup3 पर कॉल होना चाहिए, इसलिए यह रिकर्सिव है। अगर सही है, दोनों संस्करणों अतिप्रवाह, उसका नहीं बल्कि एक

(defun addup (n) 
     "Adds up the first N integers" 
     (do ((i 0 (+ i 1)) 
       (sum 0 (+ sum i))) 
      ((> i n) sum))) 

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

उत्तर

7

पूंछ कॉल अनुकूलन के लिए सामान्य लिस्प कार्यान्वयन की कोई आवश्यकता नहीं है। अधिकांश करते हैं, हालांकि (मुझे लगता है कि जावा वर्चुअल मशीन की सीमाओं के कारण एबीसीएल नहीं करता है)।

कार्यान्वयन के दस्तावेज आपको बताएंगे कि टीसीओ (यदि उपलब्ध हो) के लिए अनुकूलन सेटिंग्स का चयन किया जाना चाहिए।

यह पाशन निर्माणों में से एक का उपयोग करने के कॉमन लिस्प कोड के लिए और अधिक मुहावरेदार है:

(loop :for i :upto n 
     :sum i) 

(let ((sum 0)) 
    (dotimes (i n) 
    (incf sum (1+ i)))) 

(do ((i 0 (1+ i)) 
    (sum 0 (+ sum i))) 
    ((> i n) sum)) 

इस मामले में, ज़ाहिर है, यह "छोटी गॉस" का उपयोग करने के लिए बेहतर है:

(/ (* n (1+ n)) 2) 
+1

जैसा कि डैनले द्वारा इंगित किया गया है, पहले फ़ंक्शन में एक टाइपो है, इसे स्वयं कॉल करना चाहिए, न कि 'एडुप', जो अनिवार्य रूप से आपके द्वारा वर्णित कार्य है। यदि मैं टाइपो को सही करता हूं, तो यह भी बहती है। फिर भी, मुझे परेशान है कि 'डू' निर्माण रिकर्सिव एक से अधिक सक्षम है। मुझे clisp.org पर spec goling और ब्राउज़ करते समय सीएलआईएसपी के लिए टीसीओ के बारे में कुछ भी नहीं लगता है। अगर यह सामान्य रिकर्सन की तुलना में पूंछ की पुनरावृत्ति अधिक शक्तिशाली नहीं है तो क्या यह अजीब नहीं होगा? – oarfish

+0

आपको आश्चर्यचकित नहीं होना चाहिए। टेल कॉल ऑप्टिमाइज़ेशन केवल एक रिकर्सिव परिभाषा को निरंतर (स्टैक) स्पेस में चलाता है, ताकि यह एक पुनरावृत्ति परिभाषा के रूप में तेज़ हो सके। इसमें कोई जादू शामिल नहीं है जो इससे तेज हो सकता है। – Svante

+0

बास्की की लिस्प की भूमि में, मैंने अभी पढ़ा है कि वास्तव में सीएलआईएसपी केवल पूंछ कॉल को अनुकूलित करता है, जब कोई फ़ंक्शन संकलित करता है। वास्तव में, पूंछ रिकर्सिव एक थोड़ा तेज है, इसलिए यहां कोई रहस्य नहीं है। – oarfish

4

ठीक है, आपके addup3 बस पर सभी पर रिकर्सिव नहीं है।

(defun addup3 (n) 
    (if (= n 0) 
    0 
    (+ n (addup (- n 1))))) ; <-- 

यह addup है जो कॉल करता है। एसबीसीएल में एक सही संस्करण की कोशिश कर रहा है:

CL-USER> (defun addup3 (n) 
      (if (= n 0) 
       0 
       (+ n (addup3 (- n 1))))) 
ADDUP3 
CL-USER> (addup3 100000) 
Control stack guard page temporarily disabled: proceed with caution 
; .. 
; Evaluation aborted on #<SB-SYS:MEMORY-FAULT-ERROR {C2F19B1}>. 

जैसा कि आप उम्मीद करेंगे।

+0

सब कुछ ठीक से स्वेंटे द्वारा अनुत्तरित किया गया है। – danlei

+0

हे भगवान, कितना मूर्खतापूर्ण। मैं इस तरह के टाइपो का पता लगाने में वास्तव में बुरा हूँ। धन्यवाद। 'Addup' फ़ंक्शन जिसे मैंने ऊपर शामिल नहीं किया है, वही करता है, लेकिन 'do' निर्माण के साथ। हालांकि इसे बुलाया जाने वाला नहीं था। – oarfish

+1

चिंता न करें, हम सभी समय-समय पर इस तरह की गलतियां करते हैं और वे * स्पॉट करने के लिए कठिन हैं। – danlei

1

जीएनयू कॉमनलिस्प, जीसीएल 2.6 का उपयोग करना।12, addup2 के संकलन पूंछ कॉल को अनुकूलित करेंगे, यहाँ मैं क्या मिला है:

>(compile 'addup2)                  

Compiling /tmp/gazonk_3012_0.lsp. 
End of Pass 1. 

;; Note: Tail-recursive call of F was replaced by iteration. 
End of Pass 2. 
OPTIMIZE levels: Safety=0 (No runtime error checking), Space=0, Speed=3 
Finished compiling /tmp/gazonk_3012_0.lsp. 
Loading /tmp/gazonk_3012_0.o 
start address -T 0x9556e8 Finished loading /tmp/gazonk_3012_0.o 
#<compiled-function ADDUP2> 
NIL 
NIL 

>>(addup2 1000000)                                    

500000500000 
>(addup3 1000000) 

Error: ERROR "Invocation history stack overflow." 
Fast links are on: do (si::use-fast-links nil) for debugging 
Signalled by IF. 
ERROR "Invocation history stack overflow." 

Broken at +. Type :H for Help. 
    1 Return to top level. 

>>(compile 'addup3)                                   

Compiling /tmp/gazonk_3012_0.lsp. 
End of Pass 1. 
End of Pass 2. 
OPTIMIZE levels: Safety=0 (No runtime error checking), Space=0, Speed=3 
Finished compiling /tmp/gazonk_3012_0.lsp. 
Loading /tmp/gazonk_3012_0.o 
start address -T 0x955a00 Finished loading /tmp/gazonk_3012_0.o 
#<compiled-function ADDUP3> 
NIL 
NIL 
>>(addup3 1000000)                                    

Error: ERROR "Value stack overflow." 

आशा है कि यह मदद करता है।

संबंधित मुद्दे