原子在大地图上使用时速度很慢

atom is slow when using it with big map

我有一个使用 clojure 的 ETL,每个线程可以加载文件的不同部分,它还需要从业务密钥中获取密钥。存储业务键到键映射的数据结构是一个哈希映射,如:

{"businessKey1" 1, 
 "businessKey2" 2,
 "businessKey3" 3, 
 "businessKey4" 4, 
 "businessKey5" 5 }

当 ETL 从文件中加载数据时,它会将文件中的每一行解析为列,如果可以在映射中找到业务键列,则只需 return 键,例如,如果找到 businessKey1,则return 1. 但是如果找到了businessKey6,那么需要调用一个web service来创建一个新的key。我打算使用atom,所以当每个线程找到一个新的key时,使用atom来修改map。但是表现非常糟糕。我测试了下面的代码,很慢,而且GC很多activity.

(def a (atom {}))
(map #(swap! a (partial merge {% 1})) (range 10000))
(println a)

最好的解决方案是什么?我应该在 java 中使用 ConcurrentHashMap 吗?

性能不佳的主要原因似乎是 (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)))

您可以通过使用 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 将自动 运行 并行并合并结果。