【发布时间】:2016-09-10 19:20:56
【问题描述】:
我正在尝试解析一个包含一百万行的文件,每一行都是一个 json 字符串,其中包含有关一本书的一些信息(作者、内容等)。我正在使用iota 加载文件,因为如果我尝试使用slurp,我的程序会抛出OutOfMemoryError。我还使用cheshire 来解析字符串。该程序只需加载一个文件并计算所有书籍中的所有单词。
我的第一次尝试包括pmap 来完成繁重的工作,我认为这基本上会利用我所有的 cpu 内核。
(ns multicore-parsing.core
(:require [cheshire.core :as json]
[iota :as io]
[clojure.string :as string]
[clojure.core.reducers :as r]))
(defn words-pmap
[filename]
(letfn [(parse-with-keywords [str]
(json/parse-string str true))
(words [book]
(string/split (:contents book) #"\s+"))]
(->>
(io/vec filename)
(pmap parse-with-keywords)
(pmap words)
(r/reduce #(apply conj %1 %2) #{})
(count))))
虽然它似乎使用了所有核心,但每个核心很少使用超过 50% 的容量,我的猜测是它与 pmap 的批量大小有关,所以我偶然发现了一些 cmets 引用的relatively old question到clojure.core.reducers 库。
我决定用reducers/map重写函数:
(defn words-reducers
[filename]
(letfn [(parse-with-keywords [str]
(json/parse-string str true))
(words [book]
(string/split (:contents book) #"\s+"))]
(->>
(io/vec filename)
(r/map parse-with-keywords)
(r/map words)
(r/reduce #(apply conj %1 %2) #{})
(count))))
但是cpu使用率更差,比之前的实现还要更久:
multicore-parsing.core=> (time (words-pmap "./dummy_data.txt"))
"Elapsed time: 20899.088919 msecs"
546
multicore-parsing.core=> (time (words-reducers "./dummy_data.txt"))
"Elapsed time: 28790.976455 msecs"
546
我做错了什么? mmap加载+reducers是解析大文件时的正确方法吗?
编辑:this 是我正在使用的文件。
EDIT2:这里是iota/seq 而不是iota/vec 的时间安排:
multicore-parsing.core=> (time (words-reducers "./dummy_data.txt"))
"Elapsed time: 160981.224565 msecs"
546
multicore-parsing.core=> (time (words-pmap "./dummy_data.txt"))
"Elapsed time: 160296.482722 msecs"
546
【问题讨论】:
-
看起来
io/vec扫描整个文件以建立行所在位置的索引。如果你尝试io/seq,你会得到不同的结果吗? -
@NathanDavis 我刚试过,时代更糟。让我更新问题
-
不使 CPU 饱和:听起来它受 I/O 限制。也许使用 line-seq 会有所帮助,它会懒惰地读取行。另外,不要像这样连续两次调用
pmap。最好使用(pmap (comp words parse-with-keywords))。尝试将尽可能多的处理打包到单个pmap调用中,因为每次调用时创建多个线程都会产生大量开销。如果使用单个pmap调用完成的处理太少,则不值得使用它。 -
通常最好使用Criterium 库进行计时,尽管在您的情况下可能无关紧要。
标签: clojure reducers pmap cheshire