Параллельная карта в haskell

#haskell #parallel-processing #multicore #combinators

#haskell #параллельная обработка #Многоядерный #комбинаторы

Вопрос:

Существует ли какая-либо замена map , которая вычисляет список параллельно? Мне не нужно, чтобы она была ленивой.

Что-то вроде: pmap :: (a -> b) -> [a] -> [b] позволяет мне pmap expensive_function big_list использовать все мои ядра на 100%.

Ответ №1:

Да, смотрите параллельный пакет:

 ls `using` parList rdeepseq
  

будет оценивать каждый элемент списка параллельно с помощью rdeepseq стратегии. Обратите внимание, что использование parListChunk с хорошим значением фрагмента может повысить производительность, если ваши элементы слишком дешевы, чтобы получить выгоду от параллельной оценки каждого из них (потому что это экономит на искрообразовании для каждого элемента).

РЕДАКТИРОВАТЬ: Основываясь на вашем вопросе, я чувствую, что должен объяснить, почему это ответ. Это потому, что Haskell ленив! Рассмотрим утверждение

 let bs = map expensiveFunction as
  

Ничего не было оценено. Вы только что создали блок, который отображает expensiveFunction . Итак, как мы оцениваем его параллельно?

 let bs = map expensiveFunction as
    cs = bs `using` parList rdeepseq
  

Теперь не используйте bs list в своих будущих вычислениях, вместо этого используйте cs list. Итак, вам не нужна параллельная карта, вы можете использовать обычные (отложенные) карты и стратегию параллельного удаления.

РЕДАКТИРОВАТЬ: И если вы достаточно осмотритесь, вы увидите функцию parMap, которая выполняет то, что я показал здесь, но обернута в одну вспомогательную функцию.

В ответ на ваш комментарий, приведенный ниже код у вас не работает? у меня это работает.

 import Control.Parallel.Strategies

func as =
        let bs = map ( 1) as
            cs = bs `using` parList rdeepseq
        in cs
  

Комментарии:

1. Я пытался сделать pmap f x = (map f x) `using` parList rdeepseq , но GHC жалуется, что rdeepseq нуждается в аргументе.

2. @clark посмотрите на вставленный мной код — он должен нормально загрузиться в GHCi. Работает ли это у вас? Выражение parMap rdeepseq f as должно делать то же самое.

3. У меня не работает. «Нет экземпляра для (элемента управления. DeepSeq.NFData б) возникает в результате использования `rdeepseq'»

4. @clark вы должны использовать его в определенном контексте или с явной подписью типа. Убедитесь, что элементы вашего списка имеют NFData экземпляр, который необходим для использования rdeepseq . Если это слишком обременительно, то используйте rseq вместо этого, который вычисляет значение whnf.

5. @clark Вы скомпилировали с помощью threaded ( ghc -O2 -threaded blah.hs --make ) и использовали правильные параметры RTS ( ./blah RTS -Nx ), где x — количество ядер, которые вы хотите использовать, например 2 ? Обратите внимание, что на GHC 7 вы должны просто иметь возможность вводить ghc -O2 -threaded -with-rtsopts=-N blah.hs и запускать ./blah .

Ответ №2:

Помимо самостоятельного использования явных стратегий, как описал Том, параллельный пакет также экспортирует parMap :

  parMap :: Strategy b -> (a -> b) -> [a] -> [b]
  

где аргумент стратегии выглядит примерно так rdeepseq .

И также есть parMap в пакете par-monad (вы выходите из чистого Haskell и переходите в параллельную монаду):

  parMap :: NFData b => (a -> b) -> [a] -> Par [b]
  

Пакет par-monad задокументирован здесь .

Комментарии:

1. Здесь есть небольшое предостережение. parMap использует mapM, который является строгим. Это означает, что корешок списка полностью оценивается до начала вычислений — если список длинный, например, вы разбираете по записям, прочитанным из (огромного) файла, это, вероятно, не то, что вы хотите. Возможно, это сработало бы лучше с отложенным parMap или путем циклического распределения элементов.