Clojure 为什么这个循环函数比map慢?
我看了maps源代码,它基本上一直在创建惰性序列。我认为对集合进行迭代并将其添加到瞬态向量会更快,但显然不是这样。我对clojures的行为有什么不理解的Clojure 为什么这个循环函数比map慢?,clojure,lazy-sequences,Clojure,Lazy Sequences,我看了maps源代码,它基本上一直在创建惰性序列。我认为对集合进行迭代并将其添加到瞬态向量会更快,但显然不是这样。我对clojures的行为有什么不理解的 ;=> (time (do-with / (range 1 1000) (range 1 1000))) ;"Elapsed time: 23.1808 msecs" ; ; vs ;=> (time (doall (map #(/ %1 %2) (range 1 1000) (range 1 1000)))) ;"Elapsed
;=> (time (do-with / (range 1 1000) (range 1 1000)))
;"Elapsed time: 23.1808 msecs"
;
; vs
;=> (time (doall (map #(/ %1 %2) (range 1 1000) (range 1 1000))))
;"Elapsed time: 2.604174 msecs"
(defn do-with
[fn coll1 coll2]
(let [end (count coll1)]
(loop [i 0
res (transient [])]
(if
(= i end)
(persistent! res)
(let [x (nth coll1 i)
y (nth coll2 i)
r (fn x y)]
(recur (inc i) (conj! res r)))
))))
根据对相关结果的推测影响顺序:
do with
函数使用nth
访问输入集合中的单个项n次
在范围内以线性时间运行,使以
二次方式运行。不用说,这将降低大型集合的性能range
生成分块的seq,并且map
非常有效地处理这些seq。(本质上,它通过依次在每个输入块的内部数组上运行紧密循环,将结果放置在输出块的内部数组中,从而生成最多32个元素的块(这里实际上正好是32个元素)时间进行基准测试
不会给您带来稳定的性能。(这就是为什么人们应该真正使用适当的基准库;在Clojure的例子中,这是标准解决方案。)(map#(/%1%2)xs-ys)
可以简单地写成(map/xs-ys)
更新:
我使用(范围1 1000)
作为每种情况下的两种输入(如问题文本中),对地图
版本、原始do with
版本和新的do with
版本进行了基准测试,获得了以下平均执行时间:
;;; (range 1 1000)
new do-with 170.383334 µs
(doall (map ...)) 230.756753 µs
original do-with 15.624444 ms
此外,我使用存储在Var中的向量作为输入而不是范围(即,在开始时使用(def r(vec(range 1 1000))
,并在每个基准中使用r
作为两个集合参数)重复了基准测试。毫不奇怪,最初的do with
首先出现--nth
在向量上非常快(加上对向量使用nth
,避免了seq遍历中涉及的所有中间分配)
以下是具有线性时间复杂度的新do with
:
(defn do-with [f xs ys]
(loop [xs (seq xs)
ys (seq ys)
ret (transient [])]
(if (and xs ys)
(recur (next xs)
(next ys)
(conj! ret (f (first xs) (first ys))))
(persistent! ret))))
感谢您的详细和全面的答复。看起来我需要更清楚地知道我正在对什么类型的数据结构执行什么类型的操作。
(defn do-with [f xs ys]
(loop [xs (seq xs)
ys (seq ys)
ret (transient [])]
(if (and xs ys)
(recur (next xs)
(next ys)
(conj! ret (f (first xs) (first ys))))
(persistent! ret))))