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

alt*_*alt 3 reduce clojure fibonacci 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"
Run Code Online (Sandbox Code Playgroud)

效率不高.但是,如果我不减少序列并简单地返回值列表,(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"
Run Code Online (Sandbox Code Playgroud)

为什么reduce这个懒惰的Fibonacci序列成本太高,如何在不放弃惯用的Clojure的情况下加快速度呢?

Pio*_*dyl 7

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

要强制实现延迟序列,您可以使用,dorun如果您不需要将其保留为值,或者doall如果您想获得已实现的seq(请注意无限的seqs).

如果用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"
Run Code Online (Sandbox Code Playgroud)

我还注意到你在另一个内部定义了你的fib功能.您不应该这样做,因为它总是在命名空间的顶层定义函数.所以你的代码应该是这样的:defndefndefn

(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))))
Run Code Online (Sandbox Code Playgroud)

如果您确实想要定义本地作用域函数,可以查看letfn.

  • `lazy-seq`(或者急切的`seq`而不仅仅是`loop`ing)增加了将所有数字包装到数据结构中的开销,而不仅仅是使用裸数.如果你想要一个抽象的方法来生成序列元素,那么`lazy-seq`很有用,但是你不知道你需要多少个.它也是可组合的(不像你的'偶数 - 斐波那契 - 低于3',它只知道如何计算n个斐波那契数的总和).你的Fibonacci seq可以在很多不同的场景中使用:你可以'首先``n`元素,`drop` first`n`元素,`filter`只``偶数?`元素等 (2认同)