Clojure 为什么在本例中使用减速器时没有显著的加速?
谁能给我举一个显著加速的例子Clojure 为什么在本例中使用减速器时没有显著的加速?,clojure,parallel-processing,reducers,Clojure,Parallel Processing,Reducers,谁能给我举一个显著加速的例子 我在Mac OSX 10.7.5上运行,Java 1.7在Intel Core i7(2核)上运行。Afold版本的频率函数看起来像 (require '[clojure.core.reducers :as r]) (def data (into [] (take 10000000 (repeatedly #(rand-int 1000))))) (defn frequencies [coll] (reduce (fn [counts x] (mer
我在Mac OSX 10.7.5上运行,Java 1.7在Intel Core i7(2核)上运行。A
fold
版本的频率函数看起来像
(require '[clojure.core.reducers :as r])
(def data (into [] (take 10000000 (repeatedly #(rand-int 1000)))))
(defn frequencies [coll]
(reduce (fn [counts x]
(merge-with + counts {x 1}))
{} coll))
(defn pfrequencies [coll]
(r/reduce (fn [counts x]
(merge-with + counts {x 1}))
{} coll))
user=> (time (do (frequencies data) nil))
"Elapsed time: 29697.183 msecs"
user=> (time (do (pfrequencies data) nil))
"Elapsed time: 25273.794 msecs"
user=> (time (do (frequencies data) nil))
"Elapsed time: 25384.086 msecs"
user=> (time (do (pfrequencies data) nil))
"Elapsed time: 25778.502 msecs"
在2个磁芯上,它可能比使用瞬变的clojure.core/frequencies慢得多。至少在4个内核上,它比第一个实现快(2倍),但仍然比clojure.core/frequencies
慢
你也可以尝试一下
(defn pfrequencies [coll]
(r/fold
(fn combinef
([] {})
([x y] (merge-with + x y)))
(fn reducef
([counts x] (merge-with + counts {x 1})))
coll))
您将其称为
pffrequencies
,这与您在问题上的并行处理
标记一起表明您认为此处使用了多线程。事实并非如此,它也不是reducers库的“主要”目标
Reducer给您带来的主要好处是,您不需要为惰性序列分配许多中间cons单元。在引入减缩器之前,frequencies
将分配10000000个cons单元来创建向量的顺序视图,以供reduce
使用。既然有了约简器,向量就知道如何在不创建此类临时对象的情况下进行约简。但是这个特性已经被后移植到clojure.core/reduce
,它的行为与r/reduce
完全相同(忽略一些与此无关的次要特性)。因此,您只是将您的函数与自身的相同克隆进行基准测试
reducers库还包括折叠的概念,它可以并行地完成一些工作,然后将中间结果合并在一起。要使用它,您需要提供比减少需求更多的信息:您必须定义如何从零开始“块”;您的函数必须是关联的;您必须指定如何组合块。演示如何正确使用折叠
,以在多个线程上完成工作
然而,你不太可能从折叠中得到任何好处:除了他提到的原因(你放弃了瞬变,而不是clojure.core/frequencies
),构建一个地图并不容易并行化。如果频率中的大部分工作是添加的(就像(频率(重复1e6 1))
),那么折叠将有所帮助;但是大部分工作都是在hashmap中管理键,而hashmap最终必须是单线程的。你可以并行地构建地图,但是你必须将它们合并在一起;由于组合步骤所需的时间与块的大小成比例,而不是恒定的时间,因此,在单独的线程上进行块操作所获得的收益微乎其微。这里的答案值得认真思考。在这种特殊情况下,不需要映射,因为结果域可以很容易地预测并放入一个可以使用索引的向量中。因此,一个幼稚问题的幼稚实现应该是这样的:
(defn p2frequencies [coll]
(apply merge-with + (pmap clojure.core/frequencies (partition-all 512 coll))))
在这里,combinef将是对结果集合的1000列的简单映射添加,这应该可以忽略不计
这使reducer版本的加速比正常版本快2-3倍,尤其是在更大(10x-100x)的数据集上。使用r/fold的分区大小(可选的'n'参数)进行一些旋转可以作为微调。数据大小为1E8(至少需要6GB JVM)时使用(*16 1024)似乎是最佳选择
您甚至可以在两个版本中使用瞬态,但我没有注意到有多少改进
我知道这个版本不适合通用,但它可能会显示速度的提高,而不需要哈希管理开销。您应该使用fold
而不是reduce
,因为它几乎与core reduce相同,即使是两个core上的fold
版本也可能比使用瞬变的clojure.core/frequencies
版本慢得多。@ankur我尝试r/fold(并省略{}seed参数),我得到了这个错误:ArityException错误的参数数(0)传递给:user$pffrequencies$fn clojure.lang.AFn.throwArity(AFn.java:437)@A.Webb是的,但这不是重点。我只是想看看使用减缩器是否能有所不同。@michielworker发布您的折叠版本。谢谢,我现在知道combinef和reducef应该是什么样子了。在我的机器上仍然没有明显的加速。在我的机器(4核)的后期评论中,速度提高了2倍,但是你有没有理由说在n=1E8的情况下,1024*16是最佳的?我发现块大小(16000)比512快2-3倍,但从大约到900000(然后突然再增加150毫秒,并继续增加)的+/-50毫秒范围内的所有东西都是相同的。不,只是通过像你一样的实验发现的。有趣的是,它在更高的分区上出现,我想知道为什么
(defn freqs
[coll]
(reduce (fn [counts x] (assoc counts x (inc (get counts x))))
(vec (int-array 1000 0))
coll))
(defn rfreqs
[coll]
(r/fold
(fn combinef
([] (vec (int-array 1000 0)))
([& cols] (apply mapv + cols)))
(fn reducef
[counts x] (assoc counts x (inc (get counts x))))
coll))