Использование параллельных стратегий с монадами - PullRequest
6 голосов
/ 04 января 2011

Я часто вижу использование и объяснение параллельных стратегий Haskell, связанных с чистыми вычислениями (например, fib). Однако я не часто вижу, что он используется с монадическими конструкциями: есть ли разумная интерпретация эффекта par и связанных функций применительно к ST s или IO? Будет ли какое-либо ускорение от такого использования?

Ответы [ 2 ]

12 голосов
/ 04 января 2011

Параллелизм в монаде IO более правильно называется «Параллелизмом» и поддерживается forkIO и друзьями в модуле Control.Concurrent.

Сложность с распараллеливанием монады ST заключается в том, что ST обязательно является однопоточным - это ее цель. Существует ленивый вариант монады ST Control.Monad.ST.Lazy, который в принципе может поддерживать параллельную оценку, но я не знаю никого, кто пытался это сделать.

Существует новая монада для параллельной оценки под названием Eval , которую можно найти в последних версиях параллельного пакета . В настоящее время я рекомендую использовать монаду Eval с rpar и rseq вместо par и pseq, поскольку это приводит к более надежному и читабельному коду. Например, обычный fib пример может быть написан

fib n = if n < 2 then 1 else 
        runEval $ do
           x <- rpar (fib (n-1))
           y <- rseq (fib (n-2))
           return (x+y)
1 голос
/ 04 января 2011

В некоторых ситуациях это имеет смысл, но в целом вы не должны этого делать. Изучите следующее:

doPar =
  let a = unsafePerformIO $ someIOCalc 1
      b = unsafePerformIO $ someIOCalc 2
  in a `par` b `pseq` a+b

в doPar, вычисляется для a, затем основной поток оценивает b. Но возможно, что после того, как основной поток завершит вычисление b, он также начнет оценивать a. Теперь у вас есть два потока, оценивающих a, что означает, что некоторые из операций ввода-вывода будут выполняться дважды (или, возможно, больше). Но если один поток завершит оценку a, другой просто отбросит то, что он сделал до сих пор. Для того, чтобы это было безопасно, вам нужно несколько вещей, чтобы быть правдой:

  1. Безопасно, если действия ввода-вывода выполняются несколько раз.
  2. Безопасно выполнять только некоторые действия ввода-вывода (например, нет очистки)
  3. Действия IO свободны от любых условий гонки. Если один поток изменяет некоторые данные при оценке a, будет ли другой поток, также работающий над a, вести себя разумно? Наверное, нет.
  4. Любые иностранные вызовы реентерабельны (это необходимо для общего параллелизма, конечно)

Если ваш someIOCalc выглядит так

someIOCalc n = do
  prelaunchMissiles
  threadDelay n
  launchMissiles

это абсолютно не безопасно использовать с par и unsafePerformIO.

Теперь, стоит ли это когда-нибудь? Может быть. Искры дешевы, даже дешевле, чем потоки, поэтому теоретически это должно быть повышение производительности. На практике, возможно, не так много. У Романа Лещинского есть сообщение в блоге об этом .

Лично мне было гораздо проще рассуждать о forkIO.

...