我有一个使用clojure的ETL,每个线程可以加载文件的不同部分,它还需要从业务键获取密钥。将业务键存储到键映射的数据结构是一个哈希映射,如:
{"businessKey1" 1,
"businessKey2" 2,
"businessKey3" 3,
"businessKey4" 4,
"businessKey5" 5 }
当ETL从文件加载数据时,它会将文件中的每一行解析为列,如果可以在地图中找到业务键列,只需返回密钥,例如,如果找到businessKey1,则返回1.但如果是找到businessKey6,然后需要调用Web服务来创建新密钥。我打算使用atom,所以当每个线程找到一个新键时,使用atom来修改map。但表现太糟糕了。我测试了以下代码,它非常慢,并且有很多GC活动。
(def a (atom {}))
(map #(swap! a (partial merge {% 1})) (range 10000))
(println a)
对此最佳解决方案是什么?我应该在java中使用ConcurrentHashMap吗?
答案 0 :(得分:4)
糟糕表现的主要来源似乎是使用(partial merge {% 1})
更为惯用的形式如下:
(let [a (atom {})]
(doall (map #(swap! a merge {% 1}) (range 10000))) (println @a)))
更快的是使用assoc
而不是每次都创建临时地图:
(let [a (atom {})]
(doall (map #(swap! a assoc % 1) (range 10000))) (println @a)))
如果你想迭代seq的副作用,最好使用doseq
:
(count (let [a (atom {})] (doseq [r (range 10000)] (swap! a assoc r 1))))
原子不是必需的,你想要的东西可以表示为减少:
(count (reduce (fn [m r] (assoc m r 1)) {} (range 10000)))
答案 1 :(得分:3)
使用Clojure reducers
可以避免在这里使用原子:
(require '[clojure.core.reducers :as r])
(defn lookup [k]
; do remote call, here it just returns 1
1)
(defn f
([] {})
([acc k] (if (get acc k)
acc
(assoc acc k (lookup k)))))
(r/fold merge f (vec (range 10000)))
clojure.core.reducers/fold
将自动并行运行并合并结果。