使用大地图时原子很慢

时间:2015-01-15 13:47:55

标签: clojure

我有一个使用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吗?

2 个答案:

答案 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将自动并行运行并合并结果。