Лучшая альтернатива 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, он поработал с ней, и в последний раз я видел ранние возвращения были потрясающими.

Пример Рича, пробующего это.

http://paste.lisp.org/display/84027

Работа с fork/join, упомянутая в предыдущих ответах на эту и другие подобные темы, в конечном итоге принесла плоды в виде библиотеки редукторов, что, вероятно, стоит посмотреть.

Вы можете использовать какую-то карту / уменьшить, реализованную вручную. Также взгляните на рамки Swarmiji.

"Распределенная вычислительная система, которая помогает писать и запускать код Clojure параллельно - по всем ядрам и процессорам"

Другие вопросы по тегам