2013-05-18 4 views
7

У меня проблема с пониманием производительности функции Common Lisp (я все еще новичок). У меня есть две версии этой функции, которая просто вычисляет сумму всех целых чисел до заданного n.Common Lisp: Почему моя хвостовая рекурсивная функция вызывает переполнение стека?

Non-хвост рекурсивной версии:

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

Tail-рекурсивная версия:

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

Я пытаюсь запустить эти функции в CLISP с входом n = 1000000. Вот результат

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

*** - Program stack overflow. RESET 

Я могу работать как успешно SBCL, но без хвостовой рекурсией один быстрее (только немного, но мне кажется странным). Я просмотрел ответы Stackoverflow для ответов, но не смог найти что-то подобное. Почему я получаю переполнение стека, хотя функция tail-recursive не предназначена для размещения всех вызовов рекурсивных функций в стеке? Должен ли я интерпретировать интерпретатор/компилятор для оптимизации хвостовых вызовов? (Я прочитал что-то вроде (proclaim '(optimize (debug 1)), чтобы установить уровень отладки и оптимизировать за счет возможностей отслеживания, но я не знаю, что это делает). Возможно, ответ очевиден, а код - дерьмо, но я просто не могу понять. Помощь приветствуется.

Редактировать: danlei указал на опечатку, это должен быть вызов addup3 в первой функции, поэтому он является рекурсивным. Если исправлено, как переполнение версии, но не его один

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

Хотя это может быть более типичный способ сделать это, я нахожу странным, что хвостовая рекурсия не всегда оптимизированы, учитывая мои инструкторы хотели сказать мне, что это гораздо более эффективными и простыми.

ответ

7

Нет необходимости в реализации реализации Common Lisp для оптимизации хвостового вызова. Однако большинство из них (я думаю, что ABCL не связано с ограничениями виртуальной машины Java).

В документации по внедрению следует указать, какие параметры оптимизации следует выбрать, чтобы иметь TCO (при наличии).

Это более идиоматическое для Common Lisp кода, чтобы использовать одну из конструкций циклов:

(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

Как было указано danlei, первая функция имеет опечатку, она должна вызывать себя, а не 'addup', которая по сути является функцией, которую вы описали. Если я исправлю опечатку, она тоже переполнится. Тем не менее, я озадачен тем, что конструкция 'do' более способна, чем рекурсивная. Кажется, я ничего не вижу в отношении TCO для CLISP при поиске в Google и просмотре спецификации на clisp.org. Было бы странно, если бы хвостовая рекурсия была не более мощной, чем обычная рекурсия? – oarfish

+0

Не удивляйтесь. Оптимизация вызовов хвоста просто делает рекурсивное определение выполняемым в пространстве констант (стека), так что оно может быть таким же быстрым, как итеративное определение. Магии не было, что могло бы сделать это быстрее, чем это. – Svante

+0

В Barski's Land of Lisp я просто прочитал, что CLISP только оптимизирует хвостовые вызовы, когда компилируется функция. Фактически, хвост рекурсивный один немного быстрее, так что нет никакой тайны здесь. – oarfish

4

Ну, ваш addup3 просто не рекурсивно совсем.

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

Он называет то, что addup есть. Попытка скорректировать версию в SBCL:

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

Все остальное правит Svante. – danlei

+0

О, боже, как глупо. Я очень плохо разбираюсь в таких опечатках. Благодарю. Функция 'addup', которую я не включил выше, делает то же самое, но с конструкцией' do'. Хотя это и не предназначалось для вызова. – oarfish

+1

Не беспокойтесь, мы все время время от времени совершаем ошибки, и они * * трудно обнаружить. – danlei

1

Использование GNU CommonLisp, GCL 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." 

Надеется, что это помогает.

 Смежные вопросы

  • Нет связанных вопросов^_^