Skip to content

Commit

Permalink
Major refactor
Browse files Browse the repository at this point in the history
* Get everything working with GHC 8
* Fix all warnings (on GHC 8)
* Improve code style consistency
* Make benchmarks run in a separate process, and include GC time
* Remove the inherent memory leak (retained results) in the benchmarks
* Use -Wall for all build outputs
* Use -auto-all for profiling
* Create Data.WeakBag (a better way of managing subscribers)
* Update Data.Functor.Misc to use new primitives exposed by Data.Dependent.Map
* Add Show, GShow, and ShowTag instances for Const2
* Add Functor (Dynamic t)
* Make Dynamic a primitive supplied by Reflex implementations
* Add Incremental, a new primitive reactive datatype
* Add mergeIncremental, a merge that can have inputs added and removed efficiently
* Make all time-invariant operations on Dynamics pure
* Add a phantom type to Spider, to distinguish different Spider domains
* Add a mutex around Spider domains
* Add a way of creating new Spider domains
* Substantially refactor Spider internals to increase clarity
* Add a (much) more useful error message when an Event causality loop is detected; with profiling enabeled, it will include the stack traces of all Events participating in the loop
* Clean up tests and benchmarks
  • Loading branch information
Ryan Trinkle committed May 28, 2016
1 parent 53b06db commit d20ce36
Show file tree
Hide file tree
Showing 15 changed files with 1,916 additions and 971 deletions.
54 changes: 26 additions & 28 deletions bench/Main.hs
Original file line number Diff line number Diff line change
@@ -1,38 +1,36 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}

-- The instance for NFData (TVar a) is an orphan, but necessary here
{-# OPTIONS_GHC -fno-warn-orphans #-}

module Main where

import Data.Functor.Misc
import Control.Monad.Primitive
import Control.Monad.IO.Class
import Control.Monad.Identity
import Data.Dependent.Sum
import Control.Concurrent.STM
import Control.Applicative
import System.IO.Unsafe
import Data.IORef
import Control.DeepSeq
import Control.Exception (evaluate)
import Control.Monad
import Reflex
import Reflex.Host.Class
import System.Mem
import System.IO
import Criterion.Main

import qualified Data.Traversable as T

import qualified Data.Dependent.Map as DM

import Data.Dependent.Map (DMap)
import qualified Data.Dependent.Map as DMap

main :: IO ()
main = defaultMain
[ bgroup "micro" micros ]

#if !(MIN_VERSION_deepseq(1,4,2))
instance NFData (IORef a) where
rnf x = seq x ()
#endif

instance NFData (TVar a) where
rnf x = seq x ()
Expand All @@ -41,15 +39,14 @@ newtype WHNF a = WHNF a
instance NFData (WHNF a) where
rnf (WHNF a) = seq a ()

withSetup :: NFData b => String -> SpiderHost a -> (a -> SpiderHost b) -> Benchmark
withSetup :: NFData b => String -> SpiderHost Global a -> (a -> SpiderHost Global b) -> Benchmark
withSetup name setup action = env (WHNF <$> runSpiderHost setup) $ \ ~(WHNF a) ->
bench name . nfIO $ runSpiderHost (action a)

withSetupWHNF :: String -> SpiderHost a -> (a -> SpiderHost b) -> Benchmark
withSetupWHNF :: String -> SpiderHost Global a -> (a -> SpiderHost Global b) -> Benchmark
withSetupWHNF name setup action = env (WHNF <$> runSpiderHost setup) $ \ ~(WHNF a) ->
bench name . whnfIO $ runSpiderHost (action a)


micros :: [Benchmark]
micros =
[ bench "newIORef" $ whnfIO $ void $ newIORef ()
Expand All @@ -72,40 +69,41 @@ micros =
(\(subd, trigger) -> fireAndRead trigger (42 :: Int) subd)
, withSetupWHNF "fireEventsOnly"
(newEventWithTriggerRef >>= subscribePair)
(\(subd, trigger) -> do
(\(_, trigger) -> do
Just key <- liftIO $ readIORef trigger
fireEvents [key :=> Identity (42 :: Int)])
, withSetupWHNF "fireEventsAndRead(head/merge1)"
(setupMerge 1 >>= subscribePair)
(\(subd, t:riggers) -> fireAndRead t (42 :: Int) subd)
(\(subd, t:_) -> fireAndRead t (42 :: Int) subd)
, withSetupWHNF "fireEventsAndRead(head/merge100)"
(setupMerge 100 >>= subscribePair)
(\(subd, t:riggers) -> fireAndRead t (42 :: Int) subd)
(\(subd, t:_) -> fireAndRead t (42 :: Int) subd)
, withSetupWHNF "fireEventsAndRead(head/merge10000)"
(setupMerge 10000 >>= subscribePair)
(\(subd, t:riggers) -> fireAndRead t (42 :: Int) subd)
(\(subd, t:_) -> fireAndRead t (42 :: Int) subd)
, withSetupWHNF "fireEventsOnly(head/merge100)"
(setupMerge 100 >>= subscribePair)
(\(subd, t:riggers) -> do
(\(_, t:_) -> do
Just key <- liftIO $ readIORef t
fireEvents [key :=> Identity (42 :: Int)])
, withSetupWHNF "hold" newEventWithTriggerRef $ \(ev,trigger) -> hold (42 :: Int) ev
, withSetupWHNF "hold" newEventWithTriggerRef $ \(ev, _) -> hold (42 :: Int) ev
, withSetupWHNF "sample" (newEventWithTriggerRef >>= hold (42 :: Int) . fst) sample
]

setupMerge :: Int
-> SpiderHost (Event Spider (DM.DMap (Const2 Int a) Identity),
[IORef (Maybe (EventTrigger Spider a))])
-> SpiderHost Global ( Event (SpiderEnv Global) (DMap (Const2 Int a) Identity)
, [IORef (Maybe (EventTrigger Spider a))]
)
setupMerge num = do
(evs, triggers) <- unzip <$> replicateM 100 newEventWithTriggerRef
let !m = DM.fromList [(Const2 i) :=> v | (i,v) <- zip [0..] evs]
(evs, triggers) <- unzip <$> replicateM num newEventWithTriggerRef
let !m = DMap.fromList [(Const2 i) :=> v | (i,v) <- zip [0..] evs]
pure (merge m, triggers)

subscribePair :: (Event Spider a, b) -> SpiderHost (EventHandle Spider a, b)
subscribePair :: (Event (SpiderEnv Global) a, b) -> SpiderHost Global (EventHandle (SpiderEnv Global) a, b)
subscribePair (ev, b) = (,b) <$> subscribeEvent ev

fireAndRead :: IORef (Maybe (EventTrigger Spider a)) -> a -> EventHandle Spider b
-> SpiderHost (Maybe b)
fireAndRead :: IORef (Maybe (EventTrigger (SpiderEnv Global) a)) -> a -> EventHandle (SpiderEnv Global) b
-> SpiderHost Global (Maybe b)
fireAndRead trigger val subd = do
Just key <- liftIO $ readIORef trigger
fireEventsAndRead [key :=> Identity val] $ readEvent subd >>= T.sequence
fireEventsAndRead [key :=> Identity val] $ readEvent subd >>= sequence
5 changes: 2 additions & 3 deletions bench/RunAll.hs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import Data.Monoid
import Control.Arrow
import Control.Monad
import Data.Bool
import qualified GHC.Event as GHC
import Control.Concurrent.STM
import Data.Int
import Control.Exception
Expand Down Expand Up @@ -80,10 +79,10 @@ benchFiring runHost tc n = runHost $ do
case tc of
TestE p -> do
(h, s) <- setupFiring p
runIterations $ readSchedule s $ readEvent' h
runIterations $ readSchedule_ s $ readEvent' h
TestB p -> do
(b, s) <- runPlan p
runIterations $ readSchedule (makeDense s) $ sample b
runIterations $ readSchedule_ (makeDense s) $ sample b

waitForFinalizers :: IO ()
waitForFinalizers = do
Expand Down
28 changes: 17 additions & 11 deletions reflex.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,23 @@ bug-reports: https://github.com/reflex-frp/reflex/issues
library
hs-source-dirs: src
build-depends:
base >= 4.7 && < 4.9,
base >= 4.7 && < 4.10,
dependent-sum == 0.3.*,
dependent-map == 0.2.*,
dependent-map >= 0.2.2 && < 0.3,
semigroups >= 0.16 && < 0.19,
mtl >= 2.1 && < 2.3,
containers == 0.5.*,
these >= 0.4 && < 0.7,
primitive >= 0.5 && < 0.7,
template-haskell >= 2.9 && < 2.11,
template-haskell >= 2.9 && < 2.12,
ref-tf == 0.4.*,
exception-transformers == 0.4.*,
transformers >= 0.2,
transformers-compat >= 0.3,
haskell-src-exts >= 1.16 && < 1.18,
haskell-src-meta == 0.6.*,
syb >= 0.5 && < 0.7
syb >= 0.5 && < 0.7,
stm == 2.4.*

exposed-modules:
Reflex,
Expand All @@ -41,16 +42,18 @@ library
Reflex.Dynamic,
Reflex.Dynamic.TH,
Reflex.Host.Class,
Data.Functor.Misc
Data.Functor.Misc,
Data.WeakBag

other-extensions: TemplateHaskell
ghc-options: -Wall -fwarn-tabs -funbox-strict-fields -O2
ghc-prof-options: -auto-all


test-suite all
type: exitcode-stdio-1.0
main-is: Reflex/Test/RunAll.hs
ghc-options: -O2 -Wall
ghc-options: -O2 -Wall -rtsopts
hs-source-dirs: test
build-depends:
base,
Expand All @@ -60,8 +63,9 @@ test-suite all
containers,
dependent-map,
dependent-sum,
transformers >= 0.3 && < 0.5,
transformers >= 0.3,
MemoTrie == 0.6.*,
deepseq >= 1.3 && < 1.5,
bifunctors,
split

Expand All @@ -70,32 +74,34 @@ benchmark spider-bench
type: exitcode-stdio-1.0
hs-source-dirs: bench
main-is: Main.hs
ghc-options: -O2 -rtsopts
ghc-options: -Wall -O2 -rtsopts
build-depends:
base,
dependent-sum,
dependent-map,
transformers >= 0.3 && < 0.5,
transformers >= 0.3,
stm == 2.4.*,
deepseq >= 1.3 && < 1.5,
mtl,
primitive,
criterion == 1.1.*,
reflex


benchmark saulzar-bench
type: exitcode-stdio-1.0
hs-source-dirs: bench test
c-sources: bench-cbits/checkCapability.c
main-is: RunAll.hs
ghc-options: -O2 -rtsopts -threaded
ghc-options: -Wall -O2 -rtsopts -threaded
ghc-prof-options: -auto-all
build-depends:
base,
containers == 0.5.*,
ref-tf == 0.4,
dependent-sum,
dependent-map,
transformers >= 0.3 && < 0.5,
transformers >= 0.3,
stm == 2.4.*,
deepseq >= 1.3 && < 1.5,
mtl,
Expand Down
25 changes: 19 additions & 6 deletions src/Data/Functor/Misc.hs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
{-# LANGUAGE KindSignatures, GADTs, DeriveDataTypeable, RankNTypes, ScopedTypeVariables, PolyKinds #-}
{-# LANGUAGE KindSignatures, GADTs, DeriveDataTypeable, RankNTypes, ScopedTypeVariables, PolyKinds, FlexibleInstances, MultiParamTypeClasses, FlexibleContexts, StandaloneDeriving #-}
module Data.Functor.Misc where

import Data.GADT.Compare
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Dependent.Map (DMap, DSum (..))
import Data.GADT.Show
import Data.Dependent.Sum
import Data.Dependent.Map (DMap)
import qualified Data.Dependent.Map as DMap
import Data.Typeable hiding (Refl)
import Data.These
Expand All @@ -26,6 +28,14 @@ data Const2 :: * -> * -> * -> * where
Const2 :: k -> Const2 k v v
deriving (Typeable)

deriving instance Show k => Show (Const2 k v v)

instance Show k => GShow (Const2 k v) where
gshowsPrec n x@(Const2 _) = showsPrec n x

instance (Show k, Show (f v)) => ShowTag (Const2 k v) f where
showTaggedPrec (Const2 _) = showsPrec

instance Eq k => GEq (Const2 k v) where
geq (Const2 a) (Const2 b) =
if a == b
Expand Down Expand Up @@ -55,13 +65,13 @@ combineDMapsWithKey f mg mh = DMap.fromList $ go (DMap.toList mg) (DMap.toList m
GGT -> (hk :=> f hk (That hv)) : go gs hs'

wrapDMap :: (forall a. a -> f a) -> DMap k Identity -> DMap k f
wrapDMap f = DMap.fromDistinctAscList . map (\(k :=> Identity v) -> k :=> f v) . DMap.toAscList
wrapDMap f = DMap.mapWithKey $ \_ -> f . runIdentity

rewrapDMap :: (forall (a :: *). f a -> g a) -> DMap k f -> DMap k g
rewrapDMap f = DMap.fromDistinctAscList . map (\(k :=> v) -> k :=> f v) . DMap.toAscList
rewrapDMap f = DMap.mapWithKey $ \_ -> f

unwrapDMap :: (forall a. f a -> a) -> DMap k f -> DMap k Identity
unwrapDMap f = DMap.fromDistinctAscList . map (\(k :=> v) -> k :=> Identity (f v)) . DMap.toAscList
unwrapDMap f = DMap.mapWithKey $ \_ -> Identity . f

unwrapDMapMaybe :: (forall a. f a -> Maybe a) -> DMap k f -> DMap k Identity
unwrapDMapMaybe f m = DMap.fromDistinctAscList [k :=> Identity w | (k :=> v) <- DMap.toAscList m, Just w <- [f v]]
Expand All @@ -70,7 +80,10 @@ mapToDMap :: Map k v -> DMap (Const2 k v) Identity
mapToDMap = DMap.fromDistinctAscList . map (\(k, v) -> Const2 k :=> Identity v) . Map.toAscList

mapWithFunctorToDMap :: Map k (f v) -> DMap (Const2 k v) f
mapWithFunctorToDMap = DMap.fromDistinctAscList . map (\(k, v) -> (Const2 k) :=> v) . Map.toAscList
mapWithFunctorToDMap = DMap.fromDistinctAscList . map (\(k, v) -> Const2 k :=> v) . Map.toAscList

extractFunctorDMap :: DMap (Const2 k (f v)) Identity -> DMap (Const2 k v) f
extractFunctorDMap = DMap.fromDistinctAscList . map (\(Const2 k :=> Identity v) -> Const2 k :=> v) . DMap.toAscList

dmapToMap :: DMap (Const2 k v) Identity -> Map k v
dmapToMap = Map.fromDistinctAscList . map (\(Const2 k :=> Identity v) -> (k, v)) . DMap.toAscList
81 changes: 81 additions & 0 deletions src/Data/WeakBag.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
{-# LANGUAGE BangPatterns #-}
module Data.WeakBag (WeakBag, WeakBagTicket, empty, singleton, insert, Data.WeakBag.traverse, remove) where

import Control.Concurrent.STM
import Control.Exception
import Control.Monad
import Control.Monad.IO.Class
import Data.IORef
import System.Mem.Weak
import Data.IntMap.Strict (IntMap)
import qualified Data.IntMap.Strict as IntMap

data WeakBag a = WeakBag
{ _weakBag_nextId :: {-# UNPACK #-} !(TVar Int) --TODO: what if this wraps around?
, _weakBag_children :: {-# UNPACK #-} !(TVar (IntMap (Weak a)))
}

data WeakBagTicket a = WeakBagTicket
{ _weakBagTicket_weakItem :: {-# UNPACK #-} !(Weak a)
, _weakBagTicket_item :: {-# NOUNPACK #-} !a
}

{-# INLINE insert #-}
insert :: a -> WeakBag a -> IORef (Weak b) -> (b -> IO ()) -> IO (WeakBagTicket a)
insert a (WeakBag nextId children) wbRef finalizer = do
a' <- evaluate a
wbRef' <- evaluate wbRef
myId <- atomically $ do
myId <- readTVar nextId
writeTVar nextId $! succ myId
return myId
let cleanup = do
wb <- readIORef wbRef'
mb <- deRefWeak wb
forM_ mb $ \b -> do
isLastNode <- atomically $ do
cs <- readTVar children
let csWithoutMe = IntMap.delete myId cs
writeTVar children $! csWithoutMe
return $ IntMap.size csWithoutMe == 0
when isLastNode $ finalizer b
return ()
return ()
wa <- mkWeakPtr a' $ Just cleanup
atomically $ modifyTVar' children $ IntMap.insert myId wa
return $ WeakBagTicket
{ _weakBagTicket_weakItem = wa
, _weakBagTicket_item = a'
}

{-# INLINE empty #-}
empty :: IO (WeakBag a)
empty = do
nextId <- newTVarIO 1
children <- newTVarIO IntMap.empty
let bag = WeakBag
{ _weakBag_nextId = nextId
, _weakBag_children = children
}
return bag

{-# INLINE singleton #-}
singleton :: a -> IORef (Weak b) -> (b -> IO ()) -> IO (WeakBag a, WeakBagTicket a)
singleton a wbRef finalizer = do
bag <- empty
ticket <- insert a bag wbRef finalizer
return (bag, ticket)

{-# INLINE traverse #-}
-- | Visit every node in the given list. If new nodes are appended during the traversal, they will not be visited.
-- Every live node that was in the list when the traversal began will be visited exactly once; however, no guarantee is made about the order of the traversal.
traverse :: MonadIO m => WeakBag a -> (a -> m ()) -> m ()
traverse (WeakBag _ children) f = do
cs <- liftIO $ readTVarIO children
forM_ cs $ \c -> do
ma <- liftIO $ deRefWeak c
mapM_ f ma

{-# INLINE remove #-}
remove :: WeakBagTicket a -> IO ()
remove = finalize . _weakBagTicket_weakItem
Loading

0 comments on commit d20ce36

Please sign in to comment.