为什么减少这个懒惰的序列会减慢这个Clojure程序的速度20倍?

时间:2016-04-06 00:54:46

标签: clojure fibonacci reduce lazy-sequences take

我有一个Clojure程序,它返回even以下n斐波那契数的惰性序列的总和:

(defn sum-of-even-fibonaccis-below-1 [n]
  (defn fib [a b] (lazy-seq (cons a (fib b (+ b a)))))
  (reduce + (take-while (partial >= n) (take-nth 3 (fib 0 1)))))

(time (dotimes [n 1000] (sum-of-even-fibonaccis-below-1 4000000))) ;; => "Elapsed time: 98.764msecs"

效率不高。但是如果我不减少序列并简单地返回值(0 2 8 34 144...)的列表,那么它的工作速度可以提高20倍:

(defn sum-of-even-fibonaccis-below-2 [n]
  (defn fib [a b] (lazy-seq (cons a (fib b (+ b a)))))
  (take-while (partial >= n) (take-nth 3 (fib 0 1))))


(time (dotimes [n 1000] (sum-of-even-fibonaccis-below-2 4000000))) ;; => "Elapsed time: 5.145msecs"

为什么reduce这个懒惰的Fibonacci序列如此昂贵,如何在不放弃惯用的Clojure的情况下加快速度?

2 个答案:

答案 0 :(得分:7)

执行时间的差异是懒惰的结果。在sum-of-even-fibonaccis-below-2中,您只生成一个懒惰的斐波那契数字序列,这些数字未实现(dotimes仅调用sum-of-even-fibonaccis-below-2来创建一个惰性序列,但不会计算其所有内容)。所以实际上你的第二个time表达式不会返回一个值列表,而是一个懒惰的seq,只有当你要求它们时才会产生它的元素。

要强制实现延迟序列,如果您不需要将其保留为值,则可以使用dorun;如果您想获得已实现的序列,则可以使用doall(小心使用无限序列) )。

如果您使用sum-of-even-fibonaccis-below-2中的dorun来衡量第二个案例,那么您将获得与sum-of-even-fibonaccis-below-1相当的时间。

我的机器的结果:

(time (dotimes [n 1000] (sum-of-even-fibonaccis-below-1 4000000))) ;; => "Elapsed time: 8.544193 msecs"

(time (dotimes [n 1000] (dorun (sum-of-even-fibonaccis-below-2 4000000)))) ;; => "Elapsed time: 8.012638 msecs"

我还注意到您在另一个fib内使用defn定义了defn函数。您不应这样做,因为defn将始终在命名空间的顶层定义函数。所以你的代码应该是这样的:

(defn fib [a b] (lazy-seq (cons a (fib b (+ b a)))))

(defn sum-of-even-fibonaccis-below-1 [n]
  (reduce + (take-while (partial >= n) (take-nth 3 (fib 0 1)))))

(defn sum-of-even-fibonaccis-below-2 [n]
  (take-while (partial >= n) (take-nth 3 (fib 0 1))))

如果您确实要定义本地范围的功能,可以查看letfn

答案 1 :(得分:-1)

<强>注释

你可以重构你的功能 - 并给他们更好的名字 - 因此:

(defn fib [a b] (lazy-seq (cons a (fib b (+ b a)))))

(defn even-fibonaccis-below [n]
  (take-while (partial >= n) (take-nth 3 (fib 0 1))))

(defn sum-of-even-fibonaccis-below [n]
  (reduce + (even-fibonaccis-below n)))

这更容易理解,因此可以回答。