Я пытаюсь разобрать файл с миллионом строк, каждая строка представляет собой строку json с некоторой информацией о книге (автор, содержание и т. д.). Я использую iota для загрузки файла, так как моя программа выдает OutOfMemoryError
, если я пытаюсь использовать slurp
. Я также использую cheshire для разбора строк. Программа просто загружает файл и считает все слова во всех книгах.
Моя первая попытка включала pmap
для выполнения тяжелой работы, я полагал, что это по существу задействует все ядра моего процессора.
(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, и поэтому я наткнулся на относительно старый вопрос, где в некоторых комментариях упоминается библиотека 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))))
Но загрузка процессора хуже, и для завершения даже требуется больше времени по сравнению с предыдущей реализацией:
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 + редукторы правильным подходом при разборе большого файла?
РЕДАКТИРОВАТЬ: это файл, который я использую.
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
? - person Nathan Davis   schedule 14.05.2016pmap
, в том числе почему он часто не загружает ЦП, и немного о том, почему включение одногоpmap
в другое может привести к удивительным результатам. Кроме того, если вы в основном ищете способ насытить свой процессор, Claypoole может быть именно тем, что вам нужно. - person Ben Kovitz   schedule 15.05.2016pmap
дважды подряд. Лучше использовать(pmap (comp words parse-with-keywords))
. Постарайтесь упаковать как можно больше обработки в один вызовpmap
, потому что создание нескольких потоков каждый раз при его вызове сопряжено с большими накладными расходами. Если обработка, выполняемая с помощью одного вызоваpmap
, слишком мала, его не стоит использовать. - person Mars   schedule 15.05.2016line-seq
поможет, если он связан с вводом-выводом - я не думал - но, возможно, стоит попробовать в любом случае. - person Mars   schedule 15.05.2016iota
на самом деле требуется много времени, чтобы разделить куски. Использованиеclojure.java.io/reader
сline-seq
сократило время почти вдвое (с pmap). Я думаю, нет необходимости загружать весь файл в память, если я могу обрабатывать его построчно. - person eugecm   schedule 15.05.2016