【问题标题】:why is this looping function so slow compared to map?为什么这个循环函数比 map 慢?
【发布时间】:2013-08-05 20:12:40
【问题描述】:

我查看了基本上不断创建惰性序列的地图源代码。我认为迭代集合并添加到瞬态向量会更快,但显然不是。我对 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: 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))) 
                  ))))

【问题讨论】:

    标签: clojure lazy-sequences


    【解决方案1】:

    按照推测对相关结果的影响排序:

    1. 您的 do-with 函数使用 nth 访问输入集合中的各个项目。 nth 在范围内以线性时间运行,使 do-with 成为二次方。不用说,这会影响大型集合的性能。

    2. range 生成分块序列,map 处理这些序列非常有效。 (本质上,它通过依次在每个输入块的内部数组上运行一个紧密循环,将结果放置在输出块的内部数组中,从而产生多达 32 个元素的块 - 这里实际上正好是 32 个。)

    3. 使用time 进行基准测试并不能提供稳定的性能。 (这就是为什么人们应该真正使用适当的基准测试库;在 Clojure 的情况下,Criterium 是标准解决方案。)

    顺便说一句,(map #(/ %1 %2) xs ys) 可以简单地写成(map / xs ys)

    更新:

    我已经对map 版本、原始do-with 和带有Criterium 的新do-with 版本进行了基准测试,在每种情况下都使用(range 1 1000) 作为两个输入(如问题文本中所示),获得以下平均值执行次数:

    ;;; (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 遍历中涉及的所有中间分配)。

    ;;; (vec (range 1 1000))
    original do-with       73.975419 µs
    new do-with            87.399952 µs
    (doall (map ...))     153.493128 µs
    

    这是具有线性时间复杂度的新 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))))
    

    【讨论】:

    • 感谢您提供详细而全面的回复。看来我需要更加清楚我在对什么类型的数据结构进行什么类型的操作。
    猜你喜欢
    • 1970-01-01
    • 2015-11-29
    • 1970-01-01
    • 1970-01-01
    • 2018-03-14
    • 1970-01-01
    • 1970-01-01
    • 2017-11-09
    • 1970-01-01
    相关资源
    最近更新 更多