Common Lisp 関数のパフォーマンスを理解するのに問題があります (私はまだ初心者です)。この関数には 2 つのバージョンがあり、指定された までのすべての整数の合計を単純に計算します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)))
入力を使用して CLISP でこれらの関数を実行しようとしていますn = 1000000
。これが結果です
[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)))
より典型的な方法かもしれませんが、末尾再帰が常に最適化されているとは限らないのは奇妙だと思います。