Лучшая альтернатива pmap в Clojure для распараллеливания умеренно недорогих функций над большими данными?
Используя clojure, у меня есть очень большой объем данных в последовательности, и я хочу обрабатывать их параллельно, с относительно небольшим количеством ядер (от 4 до 8).
Самое простое, что можно сделать, это использовать pmap
вместо map
, чтобы отобразить мою функцию обработки по последовательности данных. Но издержки координации приводят к чистым потерям в моем случае.
Я думаю, что причина в том, что pmap
Предполагается, что функция, сопоставленная с данными, очень дорогая. Глядя на исходный код pmap, кажется, что future
для каждого элемента последовательности, в свою очередь, поэтому каждый вызов функции происходит в отдельном потоке (циклически изменяя количество доступных ядер).
Вот соответствующая часть исходного кода pmap:
(defn pmap
"Like map, except f is applied in parallel. Semi-lazy in that the
parallel computation stays ahead of the consumption, but doesn't
realize the entire result unless required. Only useful for
computationally intensive functions where the time of f dominates
the coordination overhead."
([f coll]
(let [n (+ 2 (.. Runtime getRuntime availableProcessors))
rets (map #(future (f %)) coll)
step (fn step [[x & xs :as vs] fs]
(lazy-seq
(if-let [s (seq fs)]
(cons (deref x) (step xs (rest s)))
(map deref vs))))]
(step rets (drop n rets))))
;; multi-collection form of pmap elided
В моем случае отображаемая функция не так дорога, но последовательность огромна (миллионы записей). Я думаю, что стоимость создания и разыменования такого большого количества фьючерсов - это то, где параллельная прибыль теряется в накладных расходах.
Мое понимание pmap
правильный?
Есть ли лучший шаблон в укупорке для такого рода более низкой стоимости, но с массовой повторной обработкой, чем pmap
? Я подумываю о том, чтобы как-то разбить последовательность данных, а затем запустить потоки на больших кусках. Это разумный подход и какие идиомы clojure будут работать?
4 ответа
Этот вопрос: как эффективно применять параллельные функции среднего веса также решает эту проблему в очень похожем контексте.
Текущий лучший ответ должен использовать partition
разбить его на куски. затем отобразите функцию карты на каждый фрагмент. затем рекомбинируйте результаты. Карта-свертка-стиль.
К сожалению, пока нет правильного ответа, но в будущем стоит обратить внимание на работу Рича с библиотекой fork / join, появившейся в Java 7. Если вы посмотрите на его ветку Par на github, он поработал с ней, и в последний раз я видел ранние возвращения были потрясающими.
Пример Рича, пробующего это.
Работа с fork/join, упомянутая в предыдущих ответах на эту и другие подобные темы, в конечном итоге принесла плоды в виде библиотеки редукторов, что, вероятно, стоит посмотреть.
Вы можете использовать какую-то карту / уменьшить, реализованную вручную. Также взгляните на рамки Swarmiji.
"Распределенная вычислительная система, которая помогает писать и запускать код Clojure параллельно - по всем ядрам и процессорам"