#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 или путем циклического распределения элементов.