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))