Параллельный Haskell с HXT - PullRequest
       28

Параллельный Haskell с HXT

0 голосов
/ 05 сентября 2018

Я пытаюсь увеличить производительность в программе, которая анализирует XML. Программа может анализировать несколько файлов XML, поэтому я подумал, что смогу выполнить это параллельно, но все мои попытки привели к снижению производительности!

Для анализа XML я использую HXT.

У меня есть функция запуска, определенная так:

run printTasks xs = pExec xs >>= return . concat >>= doPrint printTasks 1

'pExec' получает список имен файлов и определяется как:

pExec xs = do
   ex <- mapM exec xs
   as <- ex `usingIO` parList rdeepseq
   return as

где exec определяется как:

exec = runX . process

ThreadScope показывает только одну используемую нить (до самого конца).

Кто-нибудь может объяснить, почему я так неудачно распараллелил этот код?

В случае, если это поможет:

exec :: FilePath -> [CV_scene]
pExec :: [FilePath] -> IO [[CV_scene]]

data CV_scene = Scene [CV_layer] Time deriving (Show)
data CV_layer = Layer [DirtyRects] SourceCrop deriving (Show)
data Rect     = Rect Int Int Int Int deriving (Show)-- Left Top Width Height

instance NFData CV_scene where
  rnf = foldScene reduceScene
    where reduceScene l t = rnf (seq t l)

instance NFData CV_layer where
  rnf = foldLayer reduceLayer
    where reduceLayer d s = rnf (seq s d)

instance NFData Rect where
  rnf = foldRect reduceRect
    where reduceRect l t w h = rnf [l,t,w,h]

type SourceCrop = Rect
type DirtyRect  = Rect
type Time       = Int64

Заранее спасибо за помощь!

1 Ответ

0 голосов
/ 07 сентября 2018

Во-первых, похоже, вы ошибочно пометили подпись exec, которая, вероятно, должна быть:

exec :: FilePath -> IO [CV_scene]

Теперь о важной части. Я прокомментировал то, что, по вашему мнению, происходит.

pExec xs = do
   -- A. Parse the file found at each location via exec.
   ex <- mapM exec xs
   -- B. Force the lazy parsing in parallel.
   as <- ex `usingIO` parList rdeepseq
   return as

Обратите внимание, что строка A не встречается в параллельном режиме, что, как вы можете подумать, нормально, поскольку она просто настроит парсинг разбора, который выполняется параллельно в B. Это справедливое предположение и умное использование лени, но результаты ставят это под сомнение для меня.

Я подозреваю, что реализация exec вынуждает большую часть синтаксического анализа еще до того, как строка B будет даже достигнута, так что deep seq не делает много. Это очень хорошо подходит для моего эксперимента, и профилирование поддерживает это объяснение.

Без возможности протестировать ваш код я могу только сделать следующие предложения. Сначала попытайтесь отделить разбор файла от ввода-вывода и поместите разбор в стратегию параллельного выполнения. В этом случае линии A и B становятся примерно такими:

ex <- mapM readFile xs
as <- ex `usingIO` parList (rdeepseq . exec')

с exec' частью exec после чтения файла с диска.

    exec' :: FilePath -> [CVScene]

Кроме того, вам может даже не понадобиться rdeepSeq после этого изменения.

В качестве альтернативы вы можете выполнять IO и анализ параллельно, используя программную транзакционную память. Подходы STM обычно используются для отдельных потоков ввода-вывода, которые действуют скорее как сервисы, а не как чистые вычисления. Но если по какой-то причине вы не можете использовать подход, основанный на стратегиях, стоит попробовать.

import Control.Concurrent.STM.TChan --(from stm package)
import Control.Concurrent(forkIO)

pExec'' :: [FilePath] -> IO [[CVSene]]
pExec'' xs = do
  -- A. create [(Filename,TChan [CVScene])]
  tcx <- mapM (\x -> (x,) <$> newTChanIO) xs
  -- B. do the reading/parsing in separate threads
  mapM_ (forkIO . exec'') tcx
  -- C. Collect the results
  cvs <- mapM (atomically . readTChan . snd) tcx

exec'' :: [(FilePath,TChan [CVScene])] -> IO ()
exec'' (x,tch) = do
  --D. The original exec function
  cv <- exec x
  --E. Put on the channel fifo buffer
  atomically $ writeTChan tch cv

Удачи!

...