与doseq(或for)并行处理集合的有效方法?

时间:2014-01-30 19:34:10

标签: clojure

(doseq [e coll1]
  (myfunc e))

非常快,如果你关心的都是副作用。如果我希望myfunc“并行”从多个集合中获取元素,即将myfunc应用于每个集合的第一个元素,然后应用于所有第二个元素,然后应用于所有第三个元素,该怎么办?等等?请注意,这是关于fordoseq的功能的问题,但如果想要将序列作为输出,map将执行所需的操作,因此for不是没必要。

(doseq [e1 coll1
        e2 coll2]
   (myfunc e1 e2))

会将myfunc应用于两个集合中所有可能的元素组合。如果我事先知道集合的元素是什么,我可以使用:when测试来仅合并某些元素,但假设我不知道它?

一种解决方案是创建ntuples以避免笛卡尔积,但这很耗时,从而消除了首先使用doseq的速度优势:

(let [argvecs (map vector coll1 coll2)] ; seq of ntuples of interleaved vals
  (doseq [args argvecs]
     (apply myfunc args))))

(这可能比单一集合doseq慢约8倍。查看this question末尾的domap1domap17的时间。)

4 个答案:

答案 0 :(得分:4)

如果你想避免使用map创建元组的开销,你所能做的就是自己编写,作为循环/重复,手动遍历每个集合。但实际上,您仍然需要创建一个元组,以便(apply f args)可以args,其中apply是每个集合的第n项。你可以通过不列出这样的元组来保存一些缺点,但这就是全部。像这样的可变函数的大部分费用是调用{{1}},并构建列表来实现这一点。您可以通过写一个2-arity版本的doseq-sibling和3-arity来避免这种情况,并且......但是n-arity版本总是会慢一些。

答案 1 :(得分:3)

如果是你的速度,你应该打开reflection-warnings,然后查看loop-primitive(用(休息coll1)(休息时间)继续...)

快速结帐Clojure is still和效果测试框架Criterium,以确保您正在衡量正确的事情。

答案 2 :(得分:1)

使用(dorun (map f coll1 coll2 ..))(dorun (map apply f colls))

您对f提出的问题越多,就会越长。

(def a (atom 0)
(defn f [& args] (swap! a #(apply + % args)))
(def N 10000)

在单个集合中使用doseq。 lazy-seq结构可以避免开销。

(bench (doseq [e (range N)] (f e)))
Execution time mean : 4.959713 ms

(bench (dorun (map f (range N))))
Execution time mean : 5.669721 ms

在两个集合中,注意f必须添加两次而不是一次,所以我期望这需要两倍的时间。现在注意两个版本都有一些结构性开销。

(bench (let [argvecs (map vector (range N) (range N))] 
  (doseq [e argvecs] (apply f e))))
Execution time mean : 11.876843 ms

(bench (dorun (map f (range N) (range N))))
Execution time mean : 11.145435 ms

答案 3 :(得分:0)

如果我理解得很好,这就是map的作用:

(map + [1 2] [3 4])
; => (4 6)

如果您只想要效果,可以在结果地图中使用dorun

(dorun (map (comp println +) [1 2] [3 4]))
; => nil