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

Clojure 与doseq(或for)并行运行集合的有效方法?,clojure,Clojure,如果你只关心副作用的话,它的速度非常快。如果我希望myfunc以“并行”方式从多个集合中获取元素,即将myfunc应用于每个集合的第一个元素,然后应用于所有第二个元素,然后应用于所有第三个元素,等等,该怎么办。?请注意,这与for的功能和doseq一样,都是一个问题,但是如果想要一个序列作为输出,map将完成所需的操作,因此for是不必要的 (doseq [e coll1] (myfunc e)) 而是将myfunc应用于两个集合中所有可能的元素组合。如果我事先知道集合的元素是什么,我可以

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

(doseq [e coll1]
  (myfunc e))
而是将
myfunc
应用于两个集合中所有可能的元素组合。如果我事先知道集合的元素是什么,我可以使用
:when
测试来仅组合某些元素,但假设我不知道

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

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

(这可能比单个集合的速度慢8倍左右。
doseq
。请参见末尾的
domap1
domap17

如果我理解清楚,这就是
map
的作用:

(let [argvecs (map vector coll1 coll2)] ; seq of ntuples of interleaved vals
  (doseq [args argvecs]
     (apply myfunc args))))
如果只是为了效果,可以在生成的贴图中使用
dorun

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

如果您想避免使用map创建元组的开销,您所能做的就是自己编写,作为手动遍历每个集合的循环/递归。但实际上,您最终仍然需要创建一个元组,以便
(应用f args)
,其中
args
是每个集合的第n项。通过不列出这样的元组,可以节省一些cons单元格,但仅此而已。像这样的可变函数的许多开销是调用
apply
,并构建列表来完成这项工作。您可以通过编写doseq兄弟姐妹的2-arity版本和3-arity版本来避免这种情况,并且。。。但是n-arity版本总是比较慢

使用
(dorun(映射f coll1 coll2..)
(dorun(映射应用f coll))

你对
f
的要求越高,花费的时间就越长

(dorun (map (comp println +) [1 2] [3 4]))
; => nil
在单个集合上使用
doseq
。懒惰的seq结构带来了可避免的开销

(def a (atom 0)
(defn f [& args] (swap! a #(apply + % args)))
(def N 10000)
在两个集合中,注意
f
必须添加两次而不是一次,因此我预计这需要两倍的时间。注意,现在两个版本都有一些结构开销

(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

如果你追求的是速度,那么你应该打开它,也许可以查看(使用(rest coll1)(rest coll2)重现)


此外,还应快速检查和性能测试框架,以确保您测量的是正确的东西。

啊,是的,我应该提到这一点。当然,这也有同样的效果,但在单个集合上,它比doseq慢4倍多,因此似乎不是多个集合的最有效解决方案。正如noisesmith的回答所示,
mapv
速度相当快,但仍然没有
doseq
那么快。。。看看(美妙的)
doseq
实现,它似乎针对不同类型的集合进行了优化。您是否可以对非索引集合(如列表)重复测试,看看情况是否如此?查看
doseq
代码,在我看来,
dorun
替代方案应该在集合在O(1)处不可索引时与doseq一样快。好主意。我一直在传递一个持续的向量。只是用LazySeq和PersistentList进行了尝试:在我的测试中,每次
doseq
都比
map
dorun
快得多(集合长度:1000,side-effective函数在core.matrix向量中设置一个元素,元素由集合中的整数选择)。(在A.Webb的测试中,由于收集时间较长,副作用也较慢,我想两者之间的差别要小得多。)关于
应用的有用要点
——谢谢。在我用
recur
做的实验中(参见问题中的链接),它比单个集合上的
doseq
慢,因此,我不希望它比并行迭代
doseq
更好。在另一个问题中,我将目标arity与varargs与loop/recur进行了基准测试,性能差异约为150%。loop/recur和dorun map在大型集合上的性能差异非常大。有一天,我可能会复制
doseq
源代码,为
:while
等复杂
选项剥离代码,并使其并行迭代,而不进行笛卡尔积。有一天。大概我现在看起来毛茸茸的。关于Clojure guts,我可能永远也学不到那么多。我认为使
doseq
快速的主要原因是它针对分块序列进行了优化,而这一特性并不能真正转化为并行遍历多个集合。我不确定你能从这样的努力中得到什么好处。谢谢你。我会调查这些资源。是的,很好;我总是用标准做速度测试。没有它不要离开家。claj,你的答案中的链接有错吗?”“循环原语”指向与“反射警告”相同的页面。我相信
loop
/
recur
的性能将类似于顶级函数的
recur
ing
doseq
似乎比后者快(参见上述问题中链接的其他问题)。我的猜测是,它之所以更快,是因为它对序列片段进行分块的方式。(我在这里猜测——我不太理解
doseq
的这部分源代码)谢谢A.韦伯。我可以看出,通过你的
f
doseq
dorun-map
比我最近的另一个问题(你也提供了有用的评论)更接近我的功能
(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