Multithreading Clojure-高效并发地增加列表中的数字
简短版本:在Clojure中存储数百个数字的列表的正确方法是什么,其中每个数字都会增加数百万倍(可能跨越多个线程) 长版本:程序以空向量开始,其中每个值初始化为0:Multithreading Clojure-高效并发地增加列表中的数字,multithreading,performance,data-structures,concurrency,clojure,Multithreading,Performance,Data Structures,Concurrency,Clojure,简短版本:在Clojure中存储数百个数字的列表的正确方法是什么,其中每个数字都会增加数百万倍(可能跨越多个线程) 长版本:程序以空向量开始,其中每个值初始化为0: [0 0 0 0 0 0 0 0 0 ...] 然后逐行读取数百万行文件。在一行上执行一些任意计算后,程序会增加向量中的一些值。在第一行之后,向量可能如下所示: [1 1 1 2 0 1 0 1 1 ...] 第二行之后: [2 2 3 2 2 1 0 2 2 ...] 大约5000行之后,它可能看起来像: [5000 499
[0 0 0 0 0 0 0 0 0 ...]
然后逐行读取数百万行文件。在一行上执行一些任意计算后,程序会增加向量中的一些值。在第一行之后,向量可能如下所示:
[1 1 1 2 0 1 0 1 1 ...]
第二行之后:
[2 2 3 2 2 1 0 2 2 ...]
大约5000行之后,它可能看起来像:
[5000 4998 5008 5002 4225 5098 5002 5043 ...]
因为Clojure的数据结构是不可变的,仅仅使用assoc
来增加向量中的值似乎是非常浪费的,因为每个增量都会复制整个向量
在不花费所有CPU时间复制不变的数据结构的情况下,进行这种并发数据聚合的正确方法是什么?我是否应该有一个向量,其中每个元素类似于ref或atom,所有线程都递增这些共享值?或者,是否有某种线程级数据结构可以存储计数,然后最后一步是合并每个线程的计数
这可能不会在单个线程上绑定I/O,所以我猜我将在多个线程上拆分行处理。向量的长度没有限制(可能有几千个元素长),但很可能大约有100个元素长。Clojure的
vector
是持久数据结构。当在向量中更新一个元素时,它不会复制整个元素,并且需要基本上恒定的时间,这意味着O(log32n)
但似乎每次迭代都在更新向量中的几乎每个元素。也许您想参考一下。我想知道core.matrix是否有助于此目的。这有点过分,但会使更新更容易。如果您探索这条路线,我建议您尝试不同的实现(ndarray、vectorz和clatrix支持可变性),看看哪种实现最快。一种方法是将向量创建为原子向量(而不是值),然后同时更新向量中的原子
(def len 1000)
(def vec-data (into [] (repeatedly len #(atom 0))))
;Create 10 future (threads) that update the vector atoms concurrently
(doall (for [_ (range 10)]
(future (doall (map #(swap! (vec-data %) inc) (range len) )))))
我建议如下:
- 使用
API进行向量操作core.matrix
- 也许可以使用可变向量实现(假设双精度数字适合您?)
- 使用
(零向量n)
- 每个线程将在一行上进行计算,生成一个新的向量添加到累加器中
- 然后调用
对累加器向量执行可变加法(如果担心线程安全,可以使用代理或其他并发技术序列化这些加法)(add!acculator…
(def len 1000)
(def vec-data (into [] (repeatedly len #(atom 0))))
;Create 10 future (threads) that update the vector atoms concurrently
(doall (for [_ (range 10)]
(future (doall (map #(swap! (vec-data %) inc) (range len) )))))
您可以在Clojure中使用java字节数组或长数组。你只需要小心地控制你如何使用它们
例如,这里有一个质数筛,它演示了两件事
首先,它使用字节数组,并使用aset字节设置数组中的字节,然后使用aget访问这些字节。请参阅第一个let语句,其中设置了[flags(字节数组大小)]。还可以使用(长数组大小)返回长数组。但是您应该使用(aset long array index value)在该长数组中设置值
第二,与您的问题没有直接关系,它使用一个瞬态向量(标准Clojure特性)在循环结束时建立结果,然后在返回持久向量之前将该向量转换为持久向量
(defn sieve1
"Generate a vector of all prime numbers up to maxN.
maxN must be 2 or greater."
[maxN]
(when (< maxN 2)
(throw (java.lang.IllegalArgumentException. (str "parameter maxN (" maxN ") must be 2 or greater."))))
(let [size (inc maxN) ; because array is zero based
;nSqrt (dbmath/isqrt maxN)
flags (byte-array size)]
;(println (format "maxN: %s; size: %s; nSqrt: %s" maxN size, nSqrt))
; Set all flags.
(loop [i 0]
(when (<= i maxN)
(aset-byte flags i 1)
(recur (inc i))))
; Strike out all non primes before two.
; (zero and one are not prime.)
(aset-byte flags 0 0)
(aset-byte flags 1 0)
; Strike out multiples of 2.
;(println "strike out multiples of two.")
(loop [j 4]
(when (<= j maxN)
;(println (format "aset %s 0" j))
(aset-byte flags j 0)
(recur (+ j 2))))
; Strike out multiples of primes (only odd primes are now remaining)
;(println "strike out multiples of primes.")
(loop [i 3]
(when (<= i maxN)
(when (= 1 (aget flags i))
; found that i is prime.
;(println (format "discovered i is prime: i=%s;" i))
; Strike out multiples of i, starting with i^2.
(loop [j (* i i)]
(when (<= j maxN)
;(println (format "aset %s 0" j))
(aset-byte flags j 0)
(recur (+ j i))))
)
(recur (+ i 2))))
; Build result.
(let [primes (transient [2])]
(loop [i 3]
(when (<= i maxN)
(when (= 1 (aget flags i))
(conj! primes i))
(recur (+ i 2))
))
(persistent! primes))
))
(定义1)
生成一个包含所有素数的向量,直到maxN。
maxN必须为2或更大。“
[maxN]
(当(