packages feed

ConcurrentUtils-0.2.0.0: Control/Concurrent/DataParallel.hs

{-# LANGUAGE GADTs, Rank2Types, StandaloneDeriving #-}
-- | An implementation of nested data parallelism
module Control.Concurrent.DataParallel (ArrC, inject, project, newArray, A(Count, Index, Zip, Unzip, Concat, Map, Comp, Arr, Prod, Sum), optimize, eval) where

import Data.Array
import Data.Tree
import Control.Category
import Control.Arrow
import Control.Monad
import Control.Concurrent.Conc
import System.IO.Unsafe
import Prelude hiding (id, (.))

data ArrC t = ArrC !(Array Int t) !(Forest Int)

inject ar = ArrC ar [Node 0 [], Node (uncurry subtract (bounds ar) + 1) []]

project (ArrC ar _) = ar

instance Functor ArrC where
	fmap f (ArrC ar ls) = ArrC (fmap f ar) ls

newArray ls = listArray (0, length ls - 1) ls

pairUp ls = zip ls (tail ls)

instance Show (t -> u) where
	showsPrec _ _ = ("<FUNCTION>"++)

data A t u where
	-- | Constructors for caller's use
	Count :: A Int (ArrC Int)
	Index :: A (ArrC t, Int) t
	Zip :: A (ArrC t, ArrC u) (ArrC (t, u))
	Unzip :: A (ArrC (t, u)) (ArrC t, ArrC u)
	Concat :: A (ArrC (ArrC t)) (ArrC t)
	Map :: A t u -> A (ArrC t) (ArrC u)
	Comp :: A u v -> A t u -> A t v
	Arr :: (t -> u) -> A t u
	Prod :: A t u -> A v w -> A (t, v) (u, w)
	Sum :: A t u -> A v w -> A (Either t v) (Either u w)

	-- Internal constructors
	Pack :: A (ArrC (ArrC t)) (ArrC t)
	Unpack :: A (ArrC t) (ArrC (ArrC t))
	PackSum :: A (Either t (ArrC u)) (ArrC (Either t u))
	UnpackSum :: A (ArrC (Either t u)) (Either t (ArrC u))

mirror ei = either Right Left ei

deriving instance Show (A t u)

instance Category A where
	id = arr id
	(.) = Comp

instance Arrow A where
	arr = Arr
	(***) = Prod
	first a = a *** arr id
	second a = arr id *** a

instance ArrowChoice A where
	(+++) = Sum
	left a = a +++ arr id
	right a = arr id +++ a

reassociate :: A u v -> A t u -> A t v
reassociate (Comp a a2) = reassociate a . reassociate a2
reassociate x = (x .)

-- Optimizer step 1. Pushes indexes and concats to the right and separates maps/products/sums.
step :: A t u -> A t u
step (Comp (Map (Comp a a2)) a3) = step (Map (step a)) . (Map a2 . a3)
step (Comp (Map (Prod a a2)) a3) = Zip . ((Map a *** Map a2) . (Unzip . a3))
step (Comp Index (Prod (Map a) a2)) = step a . (Index . second a2)
step (Comp Index (Prod Count a)) = arr (\(i, j) -> if inRange (0, i - 1) j then j else error "DataParallel.eval: bad index") . second a
step (Comp Concat (Map (Map a))) = Map (step a) . Concat
step (Comp Concat (Map Concat)) = Concat . Concat
step (Comp (Prod (Comp a a2) a3) a4) = step (Prod (step a) id) . (Prod a2 a3 . a4)
step (Comp (Prod a (Comp a2 a3)) a4) = step (Prod id (step a2)) . (Prod a a3 . a4)
step (Comp (Sum (Comp a a2) a3) a4) = step (Sum (step a) id) . (Sum a2 a3 . a4)
step (Comp (Sum a (Comp a2 a3)) a4) = step (Sum id (step a2)) . (Sum a a3 . a4)
step (Comp a (Comp a2 a3)) = case step (a . a2) of Comp a4 a5 -> a4 . step (a5 . a3)
step a = a

-- Optimizer step 2. Replaces nested arrays with the packed representation.
step2 :: A t u -> A t u
step2 (Map (Map a)) = Unpack . step2 (Map (step2 a)) . Pack
step2 (Map a) = case step2 a of
	Map a -> Unpack . Map a . Pack
	a -> Map a
step2 (Prod a a2) = Prod (step2 a) (step2 a2)
-- Sums create the possibility of recursion trees w/ variable depth.
step2 (Sum a (Map a2)) = UnpackSum . Map (Sum (step2 a) (step2 a2)) . PackSum
step2 (Sum (Map a) a2) = arr mirror . step2 (Sum a2 (Map a)) . arr mirror
step2 (Sum a a2) = Sum (step2 a) (step2 a2)
step2 (Comp a a2) = step2 a . step2 a2
step2 a = a

-- Optimizer step 3. Removes redundant packs and zips, combines maps/products/sums, pushes zips right.
step3 :: A t u -> Maybe (A t u)
step3 (Comp (Map a) (Comp (Map a2) a3)) = Just $ Map (repetition step3 (a . a2)) . a3
step3 (Comp Zip (Prod (Map a) (Map a2))) = Just $ Map (repetition step3 (a *** a2)) . Zip
step3 (Comp Zip (Prod Count Count)) = Just $ Map (arr (\x -> (x, x))) . (Count . arr (uncurry min))
step3 (Comp Zip (Comp Unzip a)) = Just a
step3 (Comp Pack (Comp Unpack a)) = Just a
step3 (Comp PackSum (Comp UnpackSum a)) = Just a
step3 (Comp (Prod a a2) (Prod a3 a4)) = Just $ repetition step3 (a . a3) *** repetition step3 (a2 . a4)
step3 (Comp (Sum a a2) (Sum a3 a4)) = Just $ repetition step3 (a . a3) +++ repetition step3 (a2 . a4)
step3 (Comp a (Comp a2 a3)) = liftM (a .) (step3 (a2 . a3))
step3 _ = Nothing

repetition f x = maybe x (repetition f) (f x)

-- | Optimizes an arrow for parallel execution. The arrow can be optimized once, and the result saved for multiple computations.
--   (The exact output of the optimizer is subject to change.)
optimize a = repetition step3 $ reassociate (step2 $ step $ reassociate a (arr id)) (arr id)

-- | Evaluates arrows.
eval :: A t u -> t -> u
eval Count n = inject $ unsafePerformIO $ concF n (return $!)
eval Index (ArrC ar _, i) = ar ! i
eval Zip (ArrC ar _, ArrC ar2 _) = inject $ unsafePerformIO $ concF (snd (bounds ar) `min` snd (bounds ar2))
	(\i -> let x = ar ! i; y = ar2 ! i in x `seq` y `seq` return $! (x, y))
eval Unzip ar = (fmap fst ar, fmap snd ar)
eval Concat ar0 = ArrC ar [ Node (i + j) ls3 | Node i ls2 <- ls, Node j ls3 <- ls2 ] where ArrC ar ls = eval Pack ar0
eval (Map a) (ArrC ar ls) = ArrC (unsafePerformIO $ conc $ fmap ((return $!) . eval a) ar) ls
eval Pack (ArrC ar ls) = ArrC (newArray $ concatMap (elems . project) $ elems ar)
	(zipWith Node (scanl (\i (ArrC ar _) -> i + rangeSize (bounds ar)) 0 $ elems ar)
		(map (\(ArrC _ ls) -> ls) (elems ar) ++ [[]]))
eval Unpack (ArrC ar ls) = inject $ newArray $ map
	(\(Node i ls, Node j _) -> ArrC (ixmap (0, j-i-1) (+i) ar) ls)
	(pairUp ls)
eval PackSum (Left x) = inject (newArray [Left x])
eval PackSum (Right ar) = fmap Right ar
eval UnpackSum ar = either Left (\_ -> Right (fmap (\(Right x) -> x) ar)) (project ar ! 0)
eval (Comp a a2) x = eval a $ eval a2 x
eval (Arr f) x = f x
eval (Prod a a2) (x, y) = b `seq` c `seq` (b, c) where
	b = eval a x; c = eval a2 y
eval (Sum a a2) ei = either (Left . eval a) (Right . eval a2) ei