(问题来源:Fernando Abrao。)
我听说Clojure中传感器的性能优势,但我不确定如何使用它们。
假设我有一个:samplevalue
函数返回地图序列,其中一些包含小数[
{ :samplevalue 1.3, ... },
{ :othervalue -27.7, ... },
{ :samplevalue 7.5, ... },
{ :samplevalue 1.9, ... },
]
,如下所示:
:samplevalue
我想看看有多少(frequencies
(reduce #(if (not (nil? (:samplevalue %2)))
(conj %1 (.intValue (:samplevalue %2))))
[]
(qos/device-qos-range origem device qos alvo inicio fim)))
;; => {1 2, 7 1}
属于每个整数bin,如下所示:
reduce
如何将其转换为带有传感器的快速版本,以消除中间数据结构(例如(def data
(mapv hash-map
(repeat :samplevalue)
(concat (range 1e5)
(range 1e5)
(range 1e5)
(range 1e5)
(range 1e5))))
返回的结构)?可以利用多个内核进行并行处理的代码的加分点。
答案 0 :(得分:6)
(答案来源:Renzo Borgatti(@reborg)。)
首先,让我们设置一些示例数据,我们将在稍后用于性能测试。此向量包含具有相同键的500k映射。值的重叠时间是1/5。
.intValue
现在让我们用换能器进行转换。请注意,此解决方案不并行。我将您的int
简化为:samplevalue
,这也是同样的事情。此外,从每张地图中有条件地提取(keep :samplevalue sequence)
可以缩短为(remove nil? (map :samplevalue sequence))
,相当于(require '[criterium.core :refer [quick-bench]])
(quick-bench
(transduce
(comp
(keep :samplevalue)
(map int))
(completing #(assoc! %1 %2 (inc (get %1 %2 0))) persistent!)
(transient {})
data))
;; My execution time mean: 405 ms
。我们将使用Criterium进行基准测试。
frequencies
请注意,这次我们不会将frequencies
作为外部步骤。相反,我们把它编织到了操作中。就像completing
所做的那样,我们已经在瞬态哈希映射上完成了操作以获得额外的性能。我们通过使用瞬态hashmap作为种子来实现此目的,并通过调用persistent!
将(require '[clojure.core.reducers :as r])
(import '[java.util HashMap Collections Map]
'java.util.concurrent.atomic.AtomicInteger
'java.util.concurrent.ConcurrentHashMap)
(quick-bench
(let [concurrency-level (.availableProcessors (Runtime/getRuntime))
m (ConcurrentHashMap. (quot (count data) 2) 0.75 concurrency-level)
combinef (fn ([] m) ([_ _])) ; just return `m` from the combine step
rf (fn [^Map m k]
(let [^AtomicInteger v (or (.get m k) (.putIfAbsent m k (AtomicInteger. 1)))]
(when v (.incrementAndGet v))
m))
reducef ((comp (keep :samplevalue) (map int)) rf)]
(r/fold combinef reducef data)
(into {} m)))
;; My execution time mean: 70 ms
作为最终值。
我们可以使这个并行。为了获得最佳性能,我们使用可变Java ConcurrentHashMap
而不是不可变的Clojure数据结构。
fold
我们在这里使用clojure.core.reducers
库中的ConcurrentHashMap
来实现并行性。请注意,在并行环境中,任何使用的传感器都需要无状态。另请注意,nil
不支持使用into
作为键或值;幸运的是,我们不需要在这里这样做。
输出最后转换为不可变的Clojure哈希映射。您可以删除该步骤,只需使用ConcurrentHashMap实例即可在我的计算机上进行额外的加速,删除fold
步骤会使整个quick-bench
花费大约26毫秒。
编辑2017-11-20:用户@clojure最正确地指出此答案的早期版本在let
块内调用quick-bench
初始化了并发哈希映射实例,这意味着基准测试对其所有运行使用相同的实例。我将调用移至let
,使其位于itemView
区域之外。它没有显着影响结果。