lvish-1.1.1.1: Data/LVar/NatArray.hs
{-# LANGUAGE Trustworthy #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE GADTs #-}
{-|
An I-structure (array) of /positive/ numbers. A `NatArray` cannot store zeros.
This particular implementation makes a trade-off between expressiveness (monomorphic
in array contents) and efficiency. The efficiency gained of course is that the array
may be unboxed, and we don't need extra bits to store empty/full status.
/However/, relative to "Data.LVar.IStructure", there is a performance disadvantage as
well. As of [2013.09.28] and their initial release, `NatArray`s are implemented as a
/single/ `LVar`, which means they share a single wait-list of blocked computations.
If there are many computations blocking on different elements within a `NatArray`,
scalability will be much worse than with other `IStructure` implementations.
The holy grail is to get unboxed arrays and scalable blocking, but we don't have this
yet.
Finally, note that this data-structure has an EXPERIMENTAL status and may be removed
in future releases as we find better ways to support unboxed array structures with
per-element synchronization.
-}
module Data.LVar.NatArray
(
-- * Basic operations
NatArray,
newNatArray, put, get,
-- * Iteration and callbacks
forEach, forEachHP
-- -- * Quasi-deterministic operations
-- freezeSetAfter, withCallbacksThenFreeze, freezeSet,
-- -- * Higher-level derived operations
-- copy, traverseSet, traverseSet_, union, intersection,
-- cartesianProd, cartesianProds,
-- -- * Alternate versions of derived ops that expose HandlerPools they create.
-- forEachHP, traverseSetHP, traverseSetHP_,
-- cartesianProdHP, cartesianProdsHP
) where
-- import qualified Data.Vector.Unboxed as U
-- import qualified Data.Vector.Unboxed.Mutable as M
import Data.LVar.NatArray.Unsafe
import qualified Data.Vector.Storable as U
import qualified Data.Vector.Storable.Mutable as M
import Foreign.Marshal.MissingAlloc (callocBytes)
import Foreign.Marshal.Alloc (finalizerFree)
import Foreign.Storable (sizeOf, Storable)
import Foreign.ForeignPtr (newForeignPtr, withForeignPtr)
import qualified Foreign.Ptr as P
import qualified Data.Bits.Atomic as B
import Data.Bits ((.&.))
import Control.Monad (void)
import Control.Exception (throw)
import Data.IORef
import Data.Maybe (fromMaybe)
import qualified Data.Set as S
import qualified Data.LVar.IVar as IV
import qualified Data.Foldable as F
import qualified Data.Traversable as T
import Data.LVar.Generic
import Control.LVish as LV hiding (addHandler, put,get)
import Control.LVish.DeepFrz.Internal as DF
import Control.LVish.Internal as LI
import Control.LVish.SchedIdempotent (newLV, putLV, getLV, freezeLV,
freezeLVAfter, liftIO)
import qualified Control.LVish.SchedIdempotent as L
import System.IO.Unsafe (unsafeDupablePerformIO)
import Data.LVar.NatArray.Unsafe (NatArray(..))
------------------------------------------------------------------------------
-- Toggles
#define USE_CALLOC
-- A low-level optimization below.
------------------------------------------------------------------------------
unNatArray (NatArray lv) = lv
-- | Physical identity, just as with IORefs.
-- instance Eq (NatArray s v) where
-- NatArray lv1 == NatArray lv2 = state lv1 == state lv2
-- | Create a new, empty, monotonically growing 'NatArray' of a given size.
-- All entries start off as zero, which must be BOTTOM.
newNatArray :: forall elt d s . (Storable elt, Num elt) =>
Int -> Par d s (NatArray s elt)
newNatArray len = WrapPar $ fmap (NatArray . WrapLVar) $ newLV $ do
#ifdef USE_CALLOC
let bytes = sizeOf (undefined::elt) * len
mem <- callocBytes bytes
fp <- newForeignPtr finalizerFree mem
return $! M.unsafeFromForeignPtr0 fp len
#else
M.replicate len 0
#endif
-- | /O(1)/ Freeze operation that directly returns a nice, usable, representation of
-- the array data.
freezeNatArray :: Storable a => NatArray s a -> LV.Par QuasiDet s (U.Vector a)
freezeNatArray (NatArray lv) = do
-- freezeLV
-- U.unsafeFreeze (state lv))
error "FINISHME -- freezeNatArray "
-- LI.liftIO $ U.unsafeFreeze (LI.state lv)
--------------------------------------------------------------------------------
-- Instances:
-- FIXME: there is a tension here.. should NatArray really be a generic LVarData1 at all?
-- Can it really store anything in Storable!?!? Or do we need to fix it to numbers
-- to ensure the zero-trick makes sense?
{-
instance DeepFrz a => DeepFrz (NatArray s a) where
type FrzType (NatArray s a) = NatArray Frzn (FrzType a)
frz = unsafeCoerceLVar
-- | /O(1)/: Convert from a frozen `NatArray` to a plain vector.
-- This is only permitted when the `NatArray` has already been frozen.
-- This is useful for processing the result of `Control.LVish.DeepFrz.runParThenFreeze`.
fromNatArray :: NatArray Frzn a -> U.Vector a
fromNatArray (NatArray lv) = unsafeDupablePerformIO (readIORef (state lv))
-}
--------------------------------------------------------------------------------
{-# INLINE forEachHP #-}
-- | Add an (asynchronous) callback that listens for all new elements added to
-- the array, optionally enrolled in a handler pool.
forEachHP :: (Storable a, Eq a, Num a) =>
Maybe HandlerPool -- ^ pool to enroll in, if any
-> NatArray s a -- ^ array to listen to
-> (Int -> a -> Par d s ()) -- ^ callback
-> Par d s ()
forEachHP hp (NatArray (WrapLVar lv)) callb = WrapPar $ do
L.addHandler hp lv globalCB deltaCB
return ()
where
deltaCB (ix,x) = return$ Just$ unWrapPar$ callb ix x
globalCB vec = unWrapPar$
-- FIXME / TODO: need a better (parallel) for loop:
forVec vec $ \ ix elm ->
-- FIXME: When it starts off, it is SPARSE... there must be a good way to
-- avoid testing each position for zero.
if elm == 0
then return ()
else forkHP hp $ callb ix elm
{-# INLINE forVec #-}
-- | Simple for-each loops over vector elements.
forVec :: Storable a =>
M.IOVector a -> (Int -> a -> Par d s ()) -> Par d s ()
forVec vec fn = loop 0
where
len = M.length vec
loop i | i == len = return ()
| otherwise = do elm <- LI.liftIO$ M.unsafeRead vec i
fn i elm
loop (i+1)
{-# INLINE forEach #-}
-- | Add an (asynchronous) callback that listens for all new elements added to
-- the set
forEach :: (Num a, Storable a, Eq a) =>
NatArray s a -> (Int -> a -> Par d s ()) -> Par d s ()
forEach = forEachHP Nothing
{-# INLINE put #-}
-- | Put a single element in the array. That slot must be previously empty. (WHNF)
-- Strict in the element being put in the set.
put :: forall s d elt . (Storable elt, B.AtomicBits elt, Num elt, Show elt) =>
NatArray s elt -> Int -> elt -> Par d s ()
put _ !ix 0 = throw (LVarSpecificExn$ "NatArray: violation! Attempt to put zero to index: "++show ix)
put (NatArray (WrapLVar lv)) !ix !elm = WrapPar$ putLV lv (putter ix)
where putter ix vec@(M.MVector _len fptr) =
withForeignPtr fptr $ \ ptr -> do
let offset = sizeOf (undefined::elt) * ix
-- ARG, if it weren't for the idempotency requirement we could use fetchAndAdd here:
-- orig <- B.fetchAndAdd (P.plusPtr ptr offset) elm
orig <- B.compareAndSwap (P.plusPtr ptr offset) 0 elm
case orig of
0 -> return (Just (ix, elm))
i | i == elm -> return Nothing -- Allow repeated, equal puts.
| otherwise -> throw$ ConflictingPutExn$ "Multiple puts to index of a NatArray: "++
show ix++" new/old : "++show elm++"/"++show orig
{-# INLINE get #-}
-- | Wait for an indexed entry to contain a non-zero value.
--
-- Warning: this is inefficient if it needs to block, because the deltaThresh must
-- monitor EVERY new addition.
get :: forall s d elt . (Storable elt, B.AtomicBits elt, Num elt) =>
NatArray s elt -> Int -> Par d s elt
get (NatArray (WrapLVar lv)) !ix = WrapPar $
getLV lv globalThresh deltaThresh
where
globalThresh ref _frzn = do
elm <- M.read ref ix
if elm == 0
then return Nothing
else return (Just elm)
-- FIXME: we don't actually want to call the deltaThresh on every element...
-- We want more locality than that...
deltaThresh (ix2,e2) | ix == ix2 = return$! Just e2
| otherwise = return Nothing
-- | A sequential for-loop with a catch. The body of the loop gets access to a
-- special get function. This getter will not block subsequent iterations of the
-- loop. Parallelism will be introduced minimally, only as neccessary to avoid
-- blocking.
seqLoopNonblocking :: Int -> Int ->
((NatArray s elt -> Int -> Par d s elt) -> Int -> Par d s ()) ->
Par d s ()
seqLoopNonblocking start end fn = do
error "TODO - FINISHME: seqLoopNonblocking optimization"
where
par =
L.Par $ \k -> L.ClosedPar $ \q -> do
-- tripped <- globalThresh state False
-- case tripped of
-- Just b -> exec (k b) q -- already past the threshold; invoke the
-- forkHP mh child = mkPar $ \k q -> do
-- closed <- closeInPool mh child
-- Sched.pushWork q (k ()) -- "Work-first" policy.
-- -- hpMsg " [dbg-lvish] incremented and pushed work in forkInPool, now running cont" hp
-- exec closed q
undefined
{-
parFor :: (ParFuture iv p) => InclusiveRange -> (Int -> p ()) -> p ()
parFor (InclusiveRange start end) body =
do
let run (x,y) = for_ x (y+1) body
range_segments = splitInclusiveRange (4*numCapabilities) (start,end)
vars <- M.forM range_segments (\ pr -> spawn_ (run pr))
M.mapM_ get vars
return ()
splitInclusiveRange :: Int -> (Int, Int) -> [(Int, Int)]
splitInclusiveRange pieces (start,end) =
map largepiece [0..remain-1] ++
map smallpiece [remain..pieces-1]
where
len = end - start + 1 -- inclusive [start,end]
(portion, remain) = len `quotRem` pieces
largepiece i =
let offset = start + (i * (portion + 1))
in (offset, offset + portion)
smallpiece i =
let offset = start + (i * portion) + remain
in (offset, offset + portion - 1)
data InclusiveRange = InclusiveRange Int Int
-}