accelerate-cuda-0.14.0.0: Data/Array/Accelerate/CUDA/Execute/Stream.hs
{-# LANGUAGE BangPatterns #-}
-- |
-- Module : Data.Array.Accelerate.CUDA.Execute.Stream
-- Copyright : [2013] Manuel M T Chakravarty, Gabriele Keller, Trevor L. McDonell
-- License : BSD3
--
-- Maintainer : Trevor L. McDonell <tmcdonell@cse.unsw.edu.au>
-- Stability : experimental
-- Portability : non-portable (GHC extensions)
--
module Data.Array.Accelerate.CUDA.Execute.Stream (
Stream, Reservoir, new, streaming,
) where
-- friends
import Data.Array.Accelerate.CUDA.Array.Nursery ( ) -- hashable CUDA.Context instance
import Data.Array.Accelerate.CUDA.Context ( Context(..) )
import Data.Array.Accelerate.CUDA.FullList ( FullList(..) )
import Data.Array.Accelerate.CUDA.Execute.Event ( Event )
import qualified Data.Array.Accelerate.CUDA.Execute.Event as Event
import qualified Data.Array.Accelerate.CUDA.FullList as FL
import qualified Data.Array.Accelerate.CUDA.Debug as D
-- libraries
import Control.Monad ( void )
import Control.Monad.Trans ( MonadIO, liftIO )
import Control.Exception ( bracket_ )
import Control.Concurrent ( forkIO )
import Control.Concurrent.MVar ( MVar, newMVar, withMVar, mkWeakMVar )
import System.Mem.Weak ( Weak, deRefWeak )
import Foreign.CUDA.Driver.Stream ( Stream )
import qualified Foreign.CUDA.Driver as CUDA
import qualified Foreign.CUDA.Driver.Stream as Stream
import qualified Data.HashTable.IO as HT
-- Representation
-- --------------
-- The Reservoir is a place to store CUDA execution streams that are currently
-- inactive. When a new stream is requested one is provided from the reservoir
-- if available, otherwise a fresh execution stream is created.
--
type HashTable key val = HT.BasicHashTable key val
type RSV = MVar ( HashTable CUDA.Context (FullList () Stream) )
data Reservoir = Reservoir {-# UNPACK #-} !RSV
{-# UNPACK #-} !(Weak RSV)
-- Executing operations in streams
-- -------------------------------
-- Execute an operation in a unique execution stream.
--
{-# INLINE streaming #-}
streaming :: MonadIO m => Context -> Reservoir -> (Stream -> m a) -> m (Event, a)
streaming !ctx !rsv@(Reservoir !_ !weak_rsv) !action = do
stream <- liftIO $ create ctx rsv
result <- action stream
end <- liftIO $ Event.waypoint stream
liftIO $ merge (weakContext ctx) weak_rsv stream
return $ (end, result)
-- Primitive operations
-- --------------------
-- Generate a new empty reservoir. It is not necessary to pre-populate it with
-- any streams because stream creation does not cause a device synchronisation.
--
new :: IO Reservoir
new = do
tbl <- HT.new
ref <- newMVar tbl
weak <- mkWeakMVar ref (flush tbl)
return $! Reservoir ref weak
-- Create a CUDA execution stream. If an inactive stream is available for use,
-- that is returned, else a fresh stream is created.
--
{-# INLINE create #-}
create :: Context -> Reservoir -> IO Stream
create !ctx (Reservoir !ref _) = withMVar ref $ \tbl -> do
let key = deviceContext ctx
--
ms <- HT.lookup tbl key
case ms of
Nothing -> do
stream <- Stream.create []
message ("new " ++ showStream stream)
return stream
Just (FL () stream rest) -> do
case rest of
FL.Nil -> HT.delete tbl key
FL.Cons () s ss -> HT.insert tbl key (FL () s ss)
--
return stream
-- Merge a stream back into the reservoir. This is done asynchronously, once all
-- pending operations in the stream have completed.
--
{-# INLINE merge #-}
merge :: Weak CUDA.Context -> Weak RSV -> Stream -> IO ()
merge !weak_ctx !weak_rsv !stream
= void . forkIO
$ do -- wait for all operations to complete
-- Stream.block stream
-- Now check whether the context and reservoir are still active. Return
-- the stream back to the reservoir for later reuse if we can, otherwise
-- destroy it.
--
mc <- deRefWeak weak_ctx
case mc of
Nothing -> message ("finalise/dead context " ++ showStream stream)
Just ctx -> do
--
mr <- deRefWeak weak_rsv
case mr of
Nothing -> trace ("merge/free " ++ showStream stream) $ Stream.destroy stream
Just ref -> trace ("merge/save " ++ showStream stream) $ withMVar ref $ \tbl -> do
--
ms <- HT.lookup tbl ctx
case ms of
Nothing -> HT.insert tbl ctx (FL.singleton () stream)
Just ss -> HT.insert tbl ctx (FL.cons () stream ss)
-- Destroy all streams in the reservoir.
--
flush :: HashTable CUDA.Context (FullList () Stream) -> IO ()
flush !tbl =
let clean (!ctx,!ss) = do
bracket_ (CUDA.push ctx) CUDA.pop (FL.mapM_ (const Stream.destroy) ss)
HT.delete tbl ctx
in
message "flush reservoir" >> HT.mapM_ clean tbl
-- Debug
-- -----
{-# INLINE trace #-}
trace :: String -> IO a -> IO a
trace msg next = D.message D.dump_exec ("stream: " ++ msg) >> next
{-# INLINE message #-}
message :: String -> IO ()
message s = s `trace` return ()
{-# INLINE showStream #-}
showStream :: Stream -> String
showStream (Stream.Stream s) = show s