Common Lisp 함수의 성능을 이해하는 데 문제가 있습니다 (아직 초보자입니다). 필자는이 함수의 두 가지 버전을 가지고 있습니다.이 함수는 단순히 주어진 n
까지 모든 정수의 합을 계산합니다.Common Lisp : 꼬리 재귀 함수가 스택 오버플로를 일으키는 이유는 무엇입니까?
비 꼬리 재귀 버전 :
(defun addup3 (n)
(if (= n 0)
0
(+ n (addup (- n 1)))))
꼬리 재귀 버전 : I 입력 n = 1000000
와 CLISP에서이 기능을 실행하려고
(defun addup2 (n)
(labels ((f (acc k)
(if (= k 0)
acc
(f (+ acc k) (- k 1)))))
(f 0 n)))
. 여기에 결과
[2]> (addup3 1000000)
500000500000
[3]> (addup2 1000000)
*** - Program stack overflow. RESET
내가 SBCL 모두 성공적으로 실행할 수 있지만 비 꼬리 재귀 하나는 빠른 (만 조금씩, 그러나 그것은 나에게 이상한 것 같다). 내가 Stackoverflow 질문에 대한 답변을 찾았지만 비슷한 것을 찾을 수 없습니다. 꼬리 재귀 함수가 모든 재귀 함수 호출을 스택에 넣지 않도록 설계되었지만 스택 오버플로가 발생하는 이유는 무엇입니까? 꼬리 호출을 최적화하도록 인터프리터/컴파일러에 알려야합니까? (나는 디버그 레벨을 설정하고 추적 기능의 비용으로 최적화하기 위해 (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)))
, 나는 그것이 이상한 그건 말해 같은 꼬리 재귀가 항상 내 강사를 고려, 최적화되지 않은 것을 발견 그의 없습니다 훨씬 더 효율적이고 물건.
danlei가 지적한 것처럼, 첫 번째 함수는 오타가 있으며, 본질적으로 사용자가 설명하는 함수 인 'addup'가 아니라 자체 호출해야합니다. 오타를 수정하면 오버 플로우됩니다. 아직도, 나는'do' 구조가 재귀 적 구조보다 더 유능하다는 것을 당황스럽게 생각합니다. clisp.org에서 검색 및 사양을 검색 할 때 CLISP에 대한 TCO 관련 정보를 찾지 못했습니다. 꼬리 재귀가 보통 재귀보다 더 강력하지 않다면 이상하지 않습니까? – oarfish
놀랄 필요가 없습니다. 테일 호출 최적화는 재귀 정의가 상수 (스택) 공간에서 실행되도록하므로 반복 정의만큼 빠르다. 그것보다 빨리 만들 수있는 마법은 없습니다. – Svante
Barski의 Land of Lisp에서 CLISP는 실제로 함수를 컴파일 할 때 꼬리 호출을 최적화한다고 읽었습니다. 사실 꼬리 재귀 적 재귀는 조금 더 빠르므로 여기서 수수께끼는 없습니다. – oarfish