为什么递归函数比elisp中的迭代函数表现更好?

时间:2017-03-14 17:41:50

标签: recursion lisp elisp

作为对我的一个课程的测试,我们的老师要求我们测试着名的欧几里德算法的递归和非递归方法:

迭代

(defun gcdi (a b)
  (let ((x a) (y b) r)
    (while (not (zerop y))
      (setq r (mod x y) x y y r))
     x))

递归

(defun gcdr (a b)
  (if (zerop b)
     a
     (gcdr b (mod a b))))

然后我进行了测试:

(defun test-iterative ()
(setq start (float-time))
   (loop for x from 1 to 100000
     do (gcdi 14472334024676221 8944394323791464)) ; Fibonacci Numbers close to 2^64 >:)
 (- (float-time) start))

(defun test-recursive ()
(setq start (float-time))
   (loop for x from 1 to 100000
     do (gcdr 14472334024676221 8944394323791464)) ; Fibonacci Numbers close to 2^64 >:)
 (- (float-time) start))

然后我跑了计时器:

(test-recursive)

:1.359128475189209

(test-iterative)

:1.7059495449066162

所以我的问题是,为什么递归测试的执行速度比迭代测试快?迭代的迭代几乎总是比递归更好吗? elisp是个例外吗?

2 个答案:

答案 0 :(得分:11)

理论上的答案是递归版本实际上是尾部 递归,因此编译为迭代。

然而,disassembling 这些功能揭示了真相:

byte code for gcdi:
  args: (a b)
0       varref    a
1       varref    b
2       constant  nil
3       varbind   r
4       varbind   y
5       varbind   x
6       varref    y
7:1     constant  0
8       eqlsign   
9       goto-if-not-nil 2
12      constant  mod
13      varref    x
14      varref    y
15      call      2
16      varset    r
17      varref    y
18      varset    x
19      varref    r
20      dup       
21      varset    y
22      goto      1
25:2    varref    x
26      unbind    3
27      return    

VS

byte code for gcdr:
  args: (a b)
0       varref    b
1       constant  0
2       eqlsign   
3       goto-if-nil 1
6       varref    a
7       return    
8:1     constant  gcdr
9       varref    b
10      constant  mod
11      varref    a
12      varref    b
13      call      2
14      call      2
15      return    

您可以看到gcdr几乎有一半指令,但包含两个 call指令,这意味着ELisp会< strong> not ,显然,将尾递归调用转换为迭代。 但是,ELisp中的函数调用相对便宜 因此递归版本执行得更快。

PS。虽然这个问题很有意义,但答案实际上是普遍适用的(例如,相同的方法对Python和CLISP尤其有效),应该意识到选择正确的算法(例如,线性合并 - 排序而不是二次泡沫-sort)比实现的“微优化”重要得多。

答案 1 :(得分:4)

嗯......确实很奇怪,因为Emacs的函数调用(以及递归)的实现效率不高。

我刚刚评估了以下代码:

(defun sm-gcdi (a b)
  (let ((x a) (y b) r)
    (while (not (zerop y))
      (setq r (mod x y) x y y r))
    x))
(defun sm-gcdr (a b)
  (if (zerop b)
      a
    (sm-gcdr b (mod a b))))

(defun sm-test-iterative ()
  (let ((start (float-time)))
    (dotimes (_ 100000)
      (sm-gcdi 14472334024676221 8944394323791464))
    (- (float-time) start)))

(defun sm-test-recursive ()
  (let ((start (float-time)))
    (dotimes (_ 100000)
      (sm-gcdr 14472334024676221 8944394323791464))
    (- (float-time) start)))

然后尝试了M-: (sm-test-recursive)M-: (sm-test-iterative),果然迭代版本对我来说更快。然后我做了M-: (byte-compile 'sm-gcdi)M-: (byte-compile 'sm-gcdr)再次尝试,速度差异更大。

因此,您的测量结果令我感到惊讶:它们不符合我的期望,也不符合我的测试。