【问题标题】:sum string values from vector of maps clojure来自地图向量的字符串值总和 clojure
【发布时间】:2014-06-18 10:30:31
【问题描述】:

我有一个地图矢量,如下所示,尽管每个数据集中可能有多达 100 个地图:

data({ a:a b:"2" c:t}{ a:b b:"0" c:t}{ a:c b:"-4" c:t}{ a:d b:"100" c:t}{ a:e b:"50" c:t})

我需要产生 :b 的总和

values(map :b data)
sum(reduce + (map read-string values)

这给出了预期的结果,但计算时间很长,大约每秒 1/10。我正在为数十万个数据集执行此操作,因此执行此操作会占用大量处理时间。

任何人都可以建议一种更有效/更快的方法吗?

谢谢

【问题讨论】:

  • 请给出真正的 clojure 代码,而不是这个伪代码。我们可以接受。
  • 那是真正的clojure代码...
  • 为什么不将 int 存储为 int 而不是 string?性能不仅仅来自算法,它是结构+算法的结合
  • @redhands,不,不是!我试过了,我得到了一堆错误。不过,说真的,您最好在问题中提供重要信息。就像事实一样,您使用的是旧版本的 clojure(哪个?)。
  • @Ankur,谢谢我尝试解析源数据然后运行计算,但性能仍然没有真正的提高

标签: map clojure


【解决方案1】:

这是在 Clojure 1.2.1 上以略超过 1/10 秒的时间完成的 100.000 个数据集场景中的 1/10。它基本上是你的代码(这不是真正有效的 clojure 语法,但我们明白了要点),但不知何故运行速度是 10.000 倍。

;generate 10.000 datasets of 100 maps having 10 fields each

(def scenario-data
    (vec (repeatedly 10000
                     (fn [] (vec (repeatedly 100 (fn [] (zipmap
                                                            [:a :b :c :d :e :f :g :h :i :j]
                                                            (repeatedly (fn [] (str (- (rand-int 2000) 1000))))))))))))


;now map the datasets into the reduced sums of the parsed :b fields of each dataset

(time (doall (map (fn [dataset] (reduce (fn [acc mp] (+ acc (Integer/parseInt (:b mp)))) 0 dataset))
                  scenario-data)))
"Elapsed time: 120.43267 msecs"
=> (2248 -6383 7890 ...)

由于此场景占用大量内存(10000 个数据集 ~=600MB,总计算使用 ~4GB),我无法在我的家用计算机上运行 100000 个数据集场景。但是,如果我不将数据集保存在内存中,我可以运行它,而是映射一个惰性序列而不抓住它的头部..

(time (doall (map (fn [dataset] (reduce (fn [acc mp] (+ acc (Integer/parseInt (:b mp)))) 0 dataset))
                  (repeatedly 100000
                              (fn [] (repeatedly 100 (fn [] (zipmap
                                                              [:a :b :c :d :e :f :g :h :i :j]
                                                              (repeatedly (fn [] (str (- (rand-int 2000) 1000))))))))))))
"Elapsed time: 30242.371308 msecs"
=> (-4975 -843 1560 ...)

计算 100.000 个数据集版本需要 30 秒,包括生成数据所需的所有时间。使用 pmap 将时间大约减少一半(4 个内核)。

编辑:在具有足够内存的机器上创建完全实现的 100.000 个数据集需要 135 秒。在它上面运行求和代码大约需要 1500 毫秒。使用 pmap 将其减少到 ~750 毫秒。 read-string 版本慢约 3.5 倍。

TL/DR:如果有足够的内存,您发布的算法可以在 1 秒内在 100.000 个数据集场景上运行。

请发布您的完整代码,包括您如何读取数据集并将其保存在内存中,并确保这次语法和观察结果均准确无误。没有从源头懒惰地读取数据集可能更多的是内存问题。

【讨论】:

    【解决方案2】:

    一种可能性是使用并行运行的reducers

    (require '[clojure.core.reducers :as r])
    (r/reduce + (r/map read-string values)) 
    

    对于您的小型测试用例,这不会提高运行时间,但对于大型数据集应该如此。

    【讨论】:

    • 很想使用reducers,但不幸的是我使用的是旧版本的clojure,并且无法很快升级
    • 您可以使用pmap 获得一些并行性。它可以从 clojure 1.0 获得,所以你应该可以使用它。
    【解决方案3】:

    您可以尝试使用Integer/parseIntLong/parseLong 而不是更通用的read-string

    [编辑]

    使用 Clojure 1.5.1 进行的简单测试表明 parseInt 大约快 10 倍:

    user=> (time (dotimes [n 100000] (read-string "10")))
    "Elapsed time: 142.516849 msecs"
    nil
    
    user=> (time (dotimes [n 100000] (Integer/parseInt "10")))
    "Elapsed time: 12.754187 msecs"
    nil
    

    【讨论】:

    • 我试过了 (reduce + (map#(Integer/parseint %1) values)) 但没有区别
    猜你喜欢
    • 2012-07-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-12
    • 1970-01-01
    相关资源
    最近更新 更多