reflex-0.6.3: test/Reflex/Bench/Focused.hs
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecursiveDo #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeSynonymInstances #-}
module Reflex.Bench.Focused where
import Reflex
import Reflex.TestPlan
import Control.Applicative
import Control.Monad
import Control.Monad.Fix
import Control.Monad.Identity
import Data.Traversable (for, traverse)
import qualified Data.Dependent.Map as DMap
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Maybe
import Data.Functor.Misc
import Data.List
import Data.List.Split
import Data.Tuple
import Data.Monoid
import Data.Word
import Control.DeepSeq
import Prelude hiding (concat, sum)
mergeTree :: Num a => (Monoid (f [a]), Functor f) => Int -> [f a] -> f a
mergeTree n es | length es <= n = sum' es
| otherwise = mergeTree n subTrees
where
merges = chunksOf n es
subTrees = map sum' merges
sum' = fmap sum . mconcat . fmap (fmap (:[]) )
mergeListDyn :: Reflex t => [Dynamic t a] -> Dynamic t [a]
mergeListDyn = mconcat . fmap (fmap pure)
mergeTreeDyn :: (Num a, Reflex t) => Int -> [Dynamic t a] -> Dynamic t a
mergeTreeDyn n dyns | length dyns <= n = sumDyns dyns
| otherwise = mergeTreeDyn n $ fmap sumDyns merges
where
merges = chunksOf n dyns
sumDyns = fmap sum . mergeListDyn
-- N events all firing for N frames
continuous :: TestPlan t m => Word -> Word -> m [Event t Word]
continuous n frames = for [1..n] $ \i -> plan $ (, i) <$> [1..frames]
-- Fire N events once every P frames for total N frames (interspersed evenly)
occasional :: TestPlan t m => Word -> Word -> Word -> m [Event t Word]
occasional n period frames = traverse plan $ zipWith occs [1..] phases
where
phases = take (fromIntegral n) $ concat (repeat [1..period])
occs i phase = (, i) <$> [1, phase+1..frames]
-- Event which never fires (but isn't 'never')
never' :: TestPlan t m => m (Event t Word)
never' = plan []
-- Event which fires once on first frame
event :: TestPlan t m => m (Event t Word)
event = plan [(1, 0)]
-- Event which fires constantly over N frames
events :: TestPlan t m => Word -> m (Event t Word)
events n = plan $ (\i -> (i, i)) <$> [1..n]
-- N events all originating from one event
fmapFan :: Reflex t => Word -> Event t Word -> [Event t Word]
fmapFan n e = (\i -> (+i) <$> e) <$> [1..n]
-- Make a fan using the value in the event converting it to a singleton Map
-- use a fan of size n (key is the value mod n)
fanMerge :: Reflex t => Word -> Event t Word -> Event t Word
fanMerge n e = leftmost $ s <$> [0..n - 1] where
s k = select f (Const2 k)
f = fan (toMap <$> e)
toMap k = DMap.singleton (Const2 $ k `mod` n) (Identity k)
-- Dumb version of the above using simple fmapMaybe to filter
fmapFanMerge :: Reflex t => Word -> Event t Word -> Event t Word
fmapFanMerge n e = leftmost $ s <$> [0..n - 1] where
s k = flip fmapMaybe e $ \j -> if k == j `mod` n
then Just k
else Nothing
-- Create an Event with a Map which is dense to size N
denseMap :: TestPlan t m => Word -> m (Event t (Map Word Word))
denseMap n = plan [(1, m)] where
m = Map.fromList $ zip [1..n] [1..n]
iter :: (a -> a) -> Word -> a -> a
iter _ 0 a = a
iter f n a = iter f (n - 1) (f a)
iterM :: Monad m => (a -> m a) -> Word -> a -> m a
iterM _ 0 a = return a
iterM f n a = iterM f (n - 1) =<< f a
fmapChain :: (Functor f) => Word -> f Word -> f Word
fmapChain = iter (fmap (+1))
mapDynChain :: (Reflex t, MonadHold t m) => Word -> Dynamic t Word -> m (Dynamic t Word)
mapDynChain = iterM (return . fmap (+1))
joinDynChain :: (Reflex t, MonadHold t m) => Word -> Dynamic t Word -> m (Dynamic t Word)
joinDynChain = iterM (\d -> return $ join $ fmap (const d) d)
holdDynChain :: (Reflex t, MonadHold t m) => Word -> Dynamic t Word -> m (Dynamic t Word)
holdDynChain = iterM (\d -> sample (current d) >>= flip holdDyn (updated d))
buildDynChain :: (Reflex t, MonadHold t m) => Word -> Dynamic t Word -> m (Dynamic t Word)
buildDynChain = iterM (\d -> do
let b = fmap (+1) (current d)
e = fmap (*2) (updated d)
buildDynamic (sample b) e)
combineDynChain :: (Reflex t, MonadHold t m) => Word -> Dynamic t Word -> m (Dynamic t Word)
combineDynChain = iterM (\d -> return $ zipDynWith (+) d d)
pingPong :: (Reflex t, MonadHold t m, MonadFix m) => Event t a -> Event t a -> m (Event t a)
pingPong e1 e2 = do
rec
d <- foldDyn (const swap) (e1, e2) e
let e = switch (fst <$> current d)
return e
switchFactors :: (Reflex t, MonadHold t m) => Word -> Event t Word -> m (Event t Word)
switchFactors 0 e = return e
switchFactors i e = do
b <- hold ((+1) <$> e) (e <$ ffilter ((== 0) . (i `mod`)) e)
switchFactors (i - 1) (switch b)
switchChain :: (Reflex t, MonadHold t m, MonadFix m) => Word -> Event t Word -> m (Event t Word)
switchChain = iterM (\e -> pingPong e ((+1) <$> e))
switchChain2 :: (Reflex t, MonadHold t m, MonadFix m) => Word -> Event t Word -> m (Event t Word)
switchChain2 = iterM (\e -> pingPong e ((+1) <$> leftmost [e, e]))
switchPromptlyChain :: (Reflex t, MonadHold t m) => Word -> Event t Word -> m (Event t Word)
switchPromptlyChain = iterM $ \e ->
switchPromptlyDyn <$> holdDyn e (e <$ e)
coinChain :: Reflex t => Word -> Event t Word -> Event t Word
coinChain = iter $ \e -> coincidence (e <$ e)
fanMergeChain :: Reflex t => Word -> Word -> Event t Word -> Event t Word
fanMergeChain width = iter $ fanMerge width
fmapFanMergeChain :: Reflex t => Word -> Word -> Event t Word -> Event t Word
fmapFanMergeChain width = iter $ fmapFanMerge width
-- | Data type for a collection (a map) which is updated by providing differences
data UpdatedMap t k a = UpdatedMap (Map k a) (Event t (Map k (Maybe a)))
-- This will hopefully become a primitive (faster!)
switchMergeEvents :: (MonadFix m, MonadHold t m, Reflex t, Ord k) => UpdatedMap t k (Event t a) -> m (Event t (Map k a))
switchMergeEvents mapChanges = switch . fmap mergeMap <$> holdMap mapChanges
-- switchMergeMaybe :: (MonadFix m, MonadHold t m, Reflex t, Ord k) => UpdatedMap t k (Event t a) -> m (Event t (Map k a))
-- switchMergeMaybe (UpdatedMap initial changes) = switchMergeMap initial (fmap (fromMaybe never) <$> changes)
switchMergeBehaviors :: forall a t m k. (MonadFix m, MonadHold t m, Reflex t, Ord k) => UpdatedMap t k (Behavior t a) -> m (Behavior t (Map k a))
switchMergeBehaviors mapChanges = pull . joinMap <$> holdMap mapChanges
where joinMap m = traverse sample =<< sample m
-- | Turn an UpdatedMap into a Dynamic by applying the differences to the initial value
holdMapDyn :: (Reflex t, MonadHold t m, MonadFix m, Ord k) => UpdatedMap t k a -> m (Dynamic t (Map k a))
holdMapDyn (UpdatedMap initial changes) = foldDyn (flip (Map.foldrWithKey modify)) initial changes
where
modify k Nothing items = Map.delete k items
modify k (Just item) items = Map.insert k item items
-- | Hold an UpdatedMap as a behavior by applying differences to the initial value
holdMap :: (Reflex t, MonadHold t m, MonadFix m, Ord k) => UpdatedMap t k a -> m (Behavior t (Map k a))
holdMap = (current <$>) . holdMapDyn
increasingMerge :: TestPlan t m => [a] -> m (UpdatedMap t Int a)
increasingMerge xs = do
e <- planList (zipWith Map.singleton [1..] (Just <$> xs) ++ [reset])
return (UpdatedMap Map.empty e)
where
reset = Map.fromList $ zip [1..] (const Nothing <$> xs)
decreasingMerge :: (TestPlan t m) => [a] -> m (UpdatedMap t Int a)
decreasingMerge xs = do
e <- planList (zipWith Map.singleton [1..] (const Nothing <$> xs) ++ [reset])
return (UpdatedMap initial e)
where
initial = Map.fromList $ zip [1..] xs
reset = Just <$> initial
modifyMerge :: (TestPlan t m) => [a] -> m (UpdatedMap t Int a)
modifyMerge xs = do
e <- planList $ zipWith Map.singleton [1..] (Just <$> xs)
return (UpdatedMap initial e)
where
initial = Map.fromList $ zip [1..] xs
countMany :: (Reflex t, MonadHold t m, MonadFix m) => [Event t a] -> m [Behavior t Int]
countMany = traverse (fmap current . count)
countManyDyn :: (Reflex t, MonadHold t m, MonadFix m) => [Event t a] -> m [Dynamic t Int]
countManyDyn = traverse count
-- Given a function which creates a sub-network (from a single event)
-- Create a plan which uses a switch to substitute it
switches :: TestPlan t m => Word -> (forall n. (MonadHold t n, MonadFix n) => Event t Word -> n (Event t a)) -> m (Event t a)
switches numFrames f = do
es <- events numFrames
switch <$> hold never (makeEvents es)
where
makeEvents es = pushAlways (\n -> f (fmap (+n) es)) es
-- Two sets of benchmarks, one which we're interested in testing subscribe time (as well as time to fire frames)
-- the other, which we're only interested in time for running frames
subscribing :: Word -> Word -> [(String, TestCase)]
subscribing n frames =
[ testSub "fmapFanMerge" $ return . mergeList . fmapFan n
, testSub "fanMerge" $ return . fanMerge n
, testSub "fmapFan/mergeTree" $ return . mergeTree 8 . fmapFan n
, testSub "fmapChain" $ return . fmapChain n
, testSub "switchChain" $ switchChain n
, testSub "switchPromptlyChain" $ switchPromptlyChain n
, testSub "switchFactors" $ switchFactors n
, testSub "coincidenceChain" $ return . coinChain n
]
where
testSub :: (Eq a, Show a, NFData a) => String -> (forall t n. (Reflex t, MonadHold t n, MonadFix n) => Event t Word -> n (Event t a)) -> (String, TestCase)
testSub name test = testE name (switches frames test)
-- Set of benchmark to test specifically merging dynamic collections
-- a pattern which occurs frequently in reflex-dom collections
merging :: Word -> [(String, TestCase)]
merging n =
[ testE "static events" $ mergeList <$> sparse
, testE "increasing events" $ switchMergeEvents =<< increasingMerge =<< sparse
, testE "decreasing events" $ switchMergeEvents =<< decreasingMerge =<< sparse
, testE "modify events" $ switchMergeEvents =<< modifyMerge =<< sparse
, testB "static behaviors" $ pull . traverse sample <$> counters
, testB "increasing behaviors" $ switchMergeBehaviors =<< increasingMerge =<< counters
, testB "decreasing behaviors" $ switchMergeBehaviors =<< decreasingMerge =<< counters
, testB "modify behaviors" $ switchMergeBehaviors =<< modifyMerge =<< counters
-- , testE "increasing events/switchMerge" $ switchMergeMaybe =<< increasingMerge =<< sparse
-- , testE "decreasing events/switchMerge" $ switchMergeMaybe =<< decreasingMerge =<< sparse
-- , testE "modify events/switchMerge" $ switchMergeMaybe =<< modifyMerge =<< sparse
]
where
sparse :: TestPlan t m => m [Event t Word]
sparse = fmap (fmap (+1)) <$> occasional n 8 n
counters :: TestPlan t m => m [Behavior t Int]
counters = countMany =<< sparse
fans :: Word -> [(String, TestCase)]
fans n =
[ testE "fanMapChain" $ fanMergeChain n 10 <$> e
, testE "fmapFanMapChain" $ fmapFanMergeChain n 10 <$> e
, testE "fanMerge " $ fanMerge n <$> events n
, testE "fmapFanMerge " $ fmapFanMerge n <$> events n
, testE "triggerMerge" $ leftmost <$> for [1..n] (\i -> plan [(i, i)])
]
where
e :: TestPlan t m => m (Event t Word)
e = events 100
dynamics :: Word -> [(String, TestCase)]
dynamics n =
[ testE "mapDynChain" $ fmap updated $ mapDynChain n =<< d
, testE "joinDynChain" $ fmap updated $ joinDynChain n =<< d
, testE "holdDynChain" $ fmap updated $ holdDynChain n =<< d
, testE "buildDynChain" $ fmap updated $ buildDynChain n =<< d
, testE "combineDynChain" $ fmap updated $ combineDynChain n =<< d
, testE "dense mergeTree" $ fmap (updated . mergeTreeDyn 8) dense
, testE "sparse mergeTree" $ fmap (updated . mergeTreeDyn 8) sparse
, testE "mergeDyn" $ fmap (updated . fmap sum . mergeListDyn) sparse
] where
d :: TestPlan t m => m (Dynamic t Word)
d = count =<< events 10
sparse, dense :: TestPlan t m => m [Dynamic t Int]
sparse = countManyDyn =<< occasional n 8 16
dense = countManyDyn =<< continuous n 2
firing :: Word -> [(String, TestCase)]
firing n =
[ testE "fmapChain" $ fmapChain n <$> e
, testE "switchChain2" $ switchChain2 n =<< e
, testE "switchPromptlyChain" $ switchPromptlyChain n =<< e
, testE "switchFactors" $ switchFactors n =<< e
, testE "coincidenceChain" $ coinChain n <$> e
, testE "dense mergeTree" $ mergeTree 8 <$> dense
, testE "sparse mergeTree" $ mergeTree 8 <$> sparse
-- Triggers test failure in Spider
--, testE "runFrame" $ events n
, testB "sum counters" $ do
counts <- counters
return $ pull $ sum <$> traverse sample counts
, testB "pullChain" $ fmapChain n . current <$> (count =<< events 4)
, testB "pullChain2" $ do
es <- events 4
e' <- plan [(0, ())]
b <- hold (constant 0) $
pushAlways (const $ fmapChain n <$> hold 0 es) e'
return (join b)
, testB "counters/pullTree" $ mergeTree 8 <$> counters
]
where
e :: TestPlan t m => m (Event t Word)
e = events 10
dense, sparse :: TestPlan t m => m [Event t Word]
dense = continuous n 2
sparse = occasional n 8 16
counters :: TestPlan t m => m [Behavior t Int]
counters = countMany =<< sparse