meta-par-accelerate-0.3.5: Control/Monad/Par/Meta/Resources/Accelerate.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# OPTIONS_GHC -Wall #-}
{-# OPTIONS_HADDOCK ignore-exports, prune #-}
-- | Do not use his module directly. Use a /SCHEDULER/ module (such
-- as 'Control.Monad.Par.Meta.AccSMP').
--
-- This provides a component (Resource) for assembling schedulers, as well as
-- exporting a `Control.Monad.Par.Accelerate.ParAccelerate` instance.
module Control.Monad.Par.Meta.Resources.Accelerate
(
mkResource,
runAccWith, spawnAccWith, unsafeHybridWith
) where
import Control.Concurrent
import Control.Exception.Base (evaluate)
import Control.Monad
import Control.Monad.IO.Class
import Data.Array.Accelerate (Acc, Arrays)
#ifdef ACCELERATE_CUDA_BACKEND
#warning "meta-par-accelerate: Loading REAL, LIVE CUDA BACKEND..."
import qualified Data.Array.Accelerate.CUDA as Run
#else
import qualified Data.Array.Accelerate.Interpreter as Run
#endif
import Data.Concurrent.Deque.Class (ConcQueue, WSDeque)
import Data.Concurrent.Deque.Reference as R
import System.IO.Unsafe
import Text.Printf
import qualified Control.Monad.Par.Accelerate as AC
-- import qualified Control.Monad.Par.OffChip as OC
import Control.Monad.Par.Meta
-- import Control.Monad.Par.Class (new,put_)
dbg :: Bool
#ifdef DEBUG
dbg = True
#else
dbg = False
#endif
--------------------------------------------------------------------------------
-- * The `Resource` itself:
-- | A mix-in component for assembling schedulers with an Accelerate capability.
mkResource :: Resource
mkResource = Resource defaultInit defaultSteal
-- * /Internal/ Definitions
--------------------------------------------------------------------------------
-- Global structures for communicating between Par threads and GPU
-- daemon threads
{-# NOINLINE gpuOnlyQueue #-}
-- | GPU-only queue is pushed to by 'Par' workers on the right, and
-- popped by the GPU daemon on the left. No backstealing is possible
-- from this queue.
gpuOnlyQueue :: WSDeque (IO ())
gpuOnlyQueue = unsafePerformIO R.newQ
{-# NOINLINE gpuBackstealQueue #-}
-- | GPU-only queue is pushed to by 'Par' workers on the right, and
-- popped by the GPU daemon and 'Par' workers on the left.
gpuBackstealQueue :: ConcQueue (Par (), IO ())
gpuBackstealQueue = unsafePerformIO R.newQ
{-# NOINLINE resultQueue #-}
-- | Result queue is pushed to by the GPU daemon, and popped by the
-- 'Par' workers, meaning the 'WSDeque' is appropriate.
resultQueue :: WSDeque (Par ())
resultQueue = unsafePerformIO R.newQ
--------------------------------------------------------------------------------
-- See documentation for `Control.Monad.Par.Accelerate.spawnAcc`
spawnAccWith :: (Arrays a) => (Acc a -> a) -> Acc a -> Par (IVar a)
spawnAccWith runner comp = do
when dbg $ liftIO $ printf "spawning Accelerate computation\n"
iv <- new
let wrappedComp = do
when dbg $ printf$ "running Accelerate computation:\n"++show comp++"\n"
ans <- evaluate $ runner comp
R.pushL resultQueue $ do
when dbg $ liftIO $ printf "Accelerate computation finished\n"
put_ iv ans
liftIO $ R.pushR gpuOnlyQueue wrappedComp
return iv
-- | Run an Accelerate computation and wait for its result. In the
-- context of a `Par` computation this can result in better
-- performance than using an Accelerate-provided `run` function
-- directly, because this version enables the CPU work scheduler to do
-- other work while waiting for the GPU computation to complete.
--
-- Moreover, when configured with a high-performance /CPU/ Accelerate backend
-- in the future this routine can enable automatic CPU/GPU work partitioning.
--
-- The more generic version of this function is "Control.Monad.Par.OffChip.runOffChip".
runAccWith :: (Arrays a) => (Acc a -> a) -> Acc a -> Par a
runAccWith runner comp = spawnAccWith runner comp >>= get
-- | See documentation for `Control.Monad.Par.Accelerate.unsafeHybrid`
unsafeHybridWith :: Arrays b => (Acc b -> b) -> (b -> a) -> (Par a, Acc b) -> Par (IVar a)
unsafeHybridWith runner convert (parComp, accComp) = do
when dbg $ liftIO $ printf "spawning Accelerate computation\n"
iv <- new
let wrappedParComp :: Par ()
wrappedParComp = do
when dbg $ liftIO $ printf "running backstolen computation\n"
put_ iv =<< parComp
wrappedAccComp :: IO ()
wrappedAccComp = do
when dbg $ printf "running Accelerate computation\n"
-- ans <- convert $ Acc.run accComp
let ans = convert $ runner accComp
R.pushL resultQueue $ do
when dbg $ liftIO $ printf "Accelerate computation finished\n"
put_ iv ans
liftIO $ R.pushR gpuBackstealQueue (wrappedParComp, wrappedAccComp)
return iv
--------------------------------------------------------------------------------
-- | Loop for the GPU daemon; repeatedly takes work off the 'gpuQueue'
-- and runs it.
gpuDaemon :: IO ()
gpuDaemon = do
mwork <- R.tryPopL gpuOnlyQueue
case mwork of
Just work -> work
Nothing -> do
mwork2 <- R.tryPopL gpuBackstealQueue
case mwork2 of
Just (_, work) -> work
Nothing -> return ()
gpuDaemon
defaultInit :: Startup
defaultInit = St ia
where ia _ _ = do
void $ forkIO gpuDaemon
defaultSteal :: WorkSearch
defaultSteal = WS sa
where sa _ _ = do
mfinished <- R.tryPopR resultQueue
case mfinished of
finished@(Just _) -> return finished
Nothing -> fmap fst `fmap` R.tryPopL gpuBackstealQueue
--------------------------------------------------------------------------------
-- Generic instance for Meta.Par, needs to be newtype-derived for specific schedulers.
instance AC.ParAccelerate IVar Par where
getDefaultAccImpl = return Run.run -- TEMP! FIXME - don't ever use interpreter in the future.
runAccWith = runAccWith
spawnAccWith = spawnAccWith
unsafeHybridWith = unsafeHybridWith
-- compileAcc = error "Accelerate resource -- compileAcc not implemented yet"
-- instance OC.ParOffChip Acc IVar Par where
-- type OffChipConstraint a = Arrays a
-- runOffChip = runAcc
-- spawnOffChip = spawnAcc
-- unsafeHybrid = unsafeHybrid