Как оценить кортежи параллельно с использованием стратегии rpar в Haskell?

я наткнулся на проблему с Eval монады и rpar Strategy в Haskell. Рассмотрим следующий код:

module Main where

import Control.Parallel.Strategies

main :: IO ()
main = print . sum . inParallel2 $ [1..10000]

inParallel :: [Double] -> [Double]
inParallel xss = runEval . go $ xss
    where
      go []  = return []
      go (x:xs) = do
        x'  <- rpar $ x + 1
        xs' <- go xs
        return (x':xs')

inParallel2 :: [Double] -> [Double]
inParallel2 xss = runEval . go $ xss
    where
      go []  = return []
      go [x] = return $ [x + 1]
      go (x:y:xs) = do
        (x',y') <- rpar $ (x + 1, y + 1)
        xs'     <- go xs
        return (x':y':xs'

я компилирую и запускаю его так:

ghc -O2 -Wall -threaded -rtsopts -fforce-recomp -eventlog eval.hs
./eval +RTS -N3 -ls -s

когда я использую inParallel параллелизм функций работает так, как ожидалось. В выходной статистике времени выполнения я вижу:

SPARKS: 100000 (100000 converted, 0 overflowed, 0 dud, 0 GC'd, 0 fizzled)

когда я переключаюсь на inParallel2 функция весь параллелизм ушел:

SPARKS: 0 (0 converted, 0 overflowed, 0 dud, 0 GC'd, 0 fizzled)

почему оценка кортежей не работает параллельно? Я пытался заставляя кортеж перед передачей его в rpar:

rpar $!! (x + 1, y + 1)

но все равно никакого результата. Что я делаю не так?

1 ответов


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

принуждение пары перед ее передачей в rpar - это не поможет. Теперь вы оцениваете пару локально, прежде чем аннотировать уже оцененный Кортеж для возможной параллельной оценки.

вы, вероятно, хотите объединить rpar С rdeepseq стратегия, тем самым заявляя, что термин должен быть полностью оценен, если это возможно параллельно. Вы можете сделать это, сказав

(rpar `dot` rdeepseq) (x + 1, y + 1)

на dot оператор предназначен для составления стратегий.

существует, однако, еще одна проблема с вашим кодом: сопоставление шаблонов заставляет немедленную оценку и, следовательно, использование сопоставления шаблонов для rpar-аннотированные выражения обычно являются плохой идеей. В частности, строка

(x',y') <- (rpar `dot` rdeepseq) (x + 1, y + 1)

победит весь параллелизм, потому что прежде чем искра может быть подобрана для оценки другим потоком, локальный поток уже начнет оценивать его, чтобы соответствовать шаблону. Вы можете предотвратить это, используя ленивый / неопровержимый шаблон:

~(x',y') <- (rpar `dot` rdeepseq) (x + 1, y + 1)

или, альтернативно, использовать fst и snd для доступа к компонентам пары.

наконец, не ожидайте фактического ускорения, если вы создаете искры, которые так же дешевы, как добавление одного к целому числу. Хотя сами искры относительно дешевы, они не бесплатны, поэтому они работают лучше, если вычисления, которые вы аннотируете для параллельной оценки, несколько дороги.

вы можете прочитать некоторые учебники по использованию стратегий, таких как Simon Marlow's параллельное и параллельное программирование с использованием Haskell или детерминированное параллельное программирование в Хаскелл!--31-->.