on develop help sign in
Add process isolation to benchmarks
8397284Ryan Trinkle
bench-cbits/checkCapability.cnew file+209
@@ -0,0 +1,209 @@
1+#include <Rts.h>
2+
3+// The InCall structure represents either a single in-call from C to
4+// Haskell, or a worker thread.
5+typedef struct InCall_ {
6+ StgTSO * tso; // the bound TSO (or NULL for a worker)
7+
8+ StgTSO * suspended_tso; // the TSO is stashed here when we
9+ // make a foreign call (NULL otherwise);
10+
11+ Capability *suspended_cap; // The capability that the
12+ // suspended_tso is on, because
13+ // we can't read this from the TSO
14+ // without owning a Capability in the
15+ // first place.
16+
17+ SchedulerStatus rstat; // return status
18+ StgClosure ** ret; // return value
19+
20+ struct Task_ *task;
21+
22+ // When a Haskell thread makes a foreign call that re-enters
23+ // Haskell, we end up with another Task associated with the
24+ // current thread. We have to remember the whole stack of InCalls
25+ // associated with the current Task so that we can correctly
26+ // save & restore the InCall on entry to and exit from Haskell.
27+ struct InCall_ *prev_stack;
28+
29+ // Links InCalls onto suspended_ccalls, spare_incalls
30+ struct InCall_ *prev;
31+ struct InCall_ *next;
32+} InCall;
33+
34+typedef struct Task_ {
35+#if defined(THREADED_RTS)
36+ OSThreadId id; // The OS Thread ID of this task
37+
38+ Condition cond; // used for sleeping & waking up this task
39+ Mutex lock; // lock for the condition variable
40+
41+ // this flag tells the task whether it should wait on task->cond
42+ // or just continue immediately. It's a workaround for the fact
43+ // that signalling a condition variable doesn't do anything if the
44+ // thread is already running, but we want it to be sticky.
45+ rtsBool wakeup;
46+#endif
47+
48+ // This points to the Capability that the Task "belongs" to. If
49+ // the Task owns a Capability, then task->cap points to it. If
50+ // the task does not own a Capability, then either (a) if the task
51+ // is a worker, then task->cap points to the Capability it belongs
52+ // to, or (b) it is returning from a foreign call, then task->cap
53+ // points to the Capability with the returning_worker queue that this
54+ // this Task is on.
55+ //
56+ // When a task goes to sleep, it may be migrated to a different
57+ // Capability. Hence, we always check task->cap on wakeup. To
58+ // syncrhonise between the migrater and the migratee, task->lock
59+ // must be held when modifying task->cap.
60+ struct Capability_ *cap;
61+
62+ // The current top-of-stack InCall
63+ struct InCall_ *incall;
64+
65+ nat n_spare_incalls;
66+ struct InCall_ *spare_incalls;
67+
68+ rtsBool worker; // == rtsTrue if this is a worker Task
69+ rtsBool stopped; // this task has stopped or exited Haskell
70+
71+ // So that we can detect when a finalizer illegally calls back into Haskell
72+ rtsBool running_finalizers;
73+
74+ // Links tasks on the returning_tasks queue of a Capability, and
75+ // on spare_workers.
76+ struct Task_ *next;
77+
78+ // Links tasks on the all_tasks list; need ACQUIRE_LOCK(&all_tasks_mutex)
79+ struct Task_ *all_next;
80+ struct Task_ *all_prev;
81+
82+} Task;
83+
84+struct Capability_ {
85+ // State required by the STG virtual machine when running Haskell
86+ // code. During STG execution, the BaseReg register always points
87+ // to the StgRegTable of the current Capability (&cap->r).
88+ StgFunTable f;
89+ StgRegTable r;
90+
91+ nat no; // capability number.
92+
93+ // The Task currently holding this Capability. This task has
94+ // exclusive access to the contents of this Capability (apart from
95+ // returning_tasks_hd/returning_tasks_tl).
96+ // Locks required: cap->lock.
97+ Task *running_task;
98+
99+ // true if this Capability is running Haskell code, used for
100+ // catching unsafe call-ins.
101+ rtsBool in_haskell;
102+
103+ // Has there been any activity on this Capability since the last GC?
104+ nat idle;
105+
106+ rtsBool disabled;
107+
108+ // The run queue. The Task owning this Capability has exclusive
109+ // access to its run queue, so can wake up threads without
110+ // taking a lock, and the common path through the scheduler is
111+ // also lock-free.
112+ StgTSO *run_queue_hd;
113+ StgTSO *run_queue_tl;
114+
115+ // Tasks currently making safe foreign calls. Doubly-linked.
116+ // When returning, a task first acquires the Capability before
117+ // removing itself from this list, so that the GC can find all
118+ // the suspended TSOs easily. Hence, when migrating a Task from
119+ // the returning_tasks list, we must also migrate its entry from
120+ // this list.
121+ InCall *suspended_ccalls;
122+
123+ // One mutable list per generation, so we don't need to take any
124+ // locks when updating an old-generation thunk. This also lets us
125+ // keep track of which closures this CPU has been mutating, so we
126+ // can traverse them using the right thread during GC and avoid
127+ // unnecessarily moving the data from one cache to another.
128+ bdescr **mut_lists;
129+ bdescr **saved_mut_lists; // tmp use during GC
130+
131+ // block for allocating pinned objects into
132+ bdescr *pinned_object_block;
133+ // full pinned object blocks allocated since the last GC
134+ bdescr *pinned_object_blocks;
135+
136+ // per-capability weak pointer list associated with nursery (older
137+ // lists stored in generation object)
138+ StgWeak *weak_ptr_list_hd;
139+ StgWeak *weak_ptr_list_tl;
140+
141+ // Context switch flag. When non-zero, this means: stop running
142+ // Haskell code, and switch threads.
143+ int context_switch;
144+
145+ // Interrupt flag. Like the context_switch flag, this also
146+ // indicates that we should stop running Haskell code, but we do
147+ // *not* switch threads. This is used to stop a Capability in
148+ // order to do GC, for example.
149+ //
150+ // The interrupt flag is always reset before we start running
151+ // Haskell code, unlike the context_switch flag which is only
152+ // reset after we have executed the context switch.
153+ int interrupt;
154+
155+ // Total words allocated by this cap since rts start
156+ // See [Note allocation accounting] in Storage.c
157+ W_ total_allocated;
158+
159+#if defined(THREADED_RTS)
160+ // Worker Tasks waiting in the wings. Singly-linked.
161+ Task *spare_workers;
162+ nat n_spare_workers; // count of above
163+
164+ // This lock protects:
165+ // running_task
166+ // returning_tasks_{hd,tl}
167+ // wakeup_queue
168+ // inbox
169+ Mutex lock;
170+
171+ // Tasks waiting to return from a foreign call, or waiting to make
172+ // a new call-in using this Capability (NULL if empty).
173+ // NB. this field needs to be modified by tasks other than the
174+ // running_task, so it requires cap->lock to modify. A task can
175+ // check whether it is NULL without taking the lock, however.
176+ Task *returning_tasks_hd; // Singly-linked, with head/tail
177+ Task *returning_tasks_tl;
178+
179+ // Messages, or END_TSO_QUEUE.
180+ // Locks required: cap->lock
181+ Message *inbox;
182+
183+ SparkPool *sparks;
184+
185+ // Stats on spark creation/conversion
186+ SparkCounters spark_stats;
187+#if !defined(mingw32_HOST_OS)
188+ // IO manager for this cap
189+ int io_manager_control_wr_fd;
190+#endif
191+#endif
192+
193+ // Per-capability STM-related data
194+ StgTVarWatchQueue *free_tvar_watch_queues;
195+ StgInvariantCheckQueue *free_invariant_check_queues;
196+ StgTRecChunk *free_trec_chunks;
197+ StgTRecHeader *free_trec_headers;
198+ nat transaction_tokens;
199+} // typedef Capability is defined in RtsAPI.h
200+ // We never want a Capability to overlap a cache line with anything
201+ // else, so round it up to a cache line size:
202+#ifndef mingw32_HOST_OS
203+ ATTRIBUTE_ALIGNED(64)
204+#endif
205+ ;
206+
207+HsBool myCapabilityHasOtherRunnableThreads() {
208+ return rts_unsafeGetMyCapability()->run_queue_hd == END_TSO_QUEUE ? HS_BOOL_FALSE : HS_BOOL_TRUE;
209+}
bench/RunAll.hs+95−37
@@ -1,4 +1,4 @@
1−{-# LANGUAGE ConstraintKinds, TypeSynonymInstances, BangPatterns, ScopedTypeVariables, TupleSections, GADTs, RankNTypes, FlexibleInstances, FlexibleContexts, MultiParamTypeClasses, GeneralizedNewtypeDeriving #-}
1+{-# LANGUAGE ConstraintKinds, TypeSynonymInstances, BangPatterns, ScopedTypeVariables, TupleSections, GADTs, RankNTypes, FlexibleInstances, FlexibleContexts, MultiParamTypeClasses, GeneralizedNewtypeDeriving, ForeignFunctionInterface, ViewPatterns, TemplateHaskell, PatternSynonyms #-}
2{-# OPTIONS_GHC -fno-warn-orphans #-}
2{-# OPTIONS_GHC -fno-warn-orphans #-}
3
3
4module Main where
4module Main where
@@ -22,14 +22,39 @@ import System.IO
22import System.Mem
22import System.Mem
23import Prelude
23import Prelude
24
24
25+import Data.IORef
26+import System.Mem.Weak
27+import Data.Function
28+import Control.Concurrent
29+import Data.Time.Clock
30+import GHC.Stats
31+import Data.Monoid
32+import Control.Arrow
33+import Control.Monad
34+import Data.Bool
35+import qualified GHC.Event as GHC
36+import Control.Concurrent.STM
37+import Data.Int
38+import Control.Exception
39+import Text.Read
40+import System.Environment
41+import Debug.Trace.LocationTH
42+import Control.Monad.Trans
43+import System.Process
44+
45+import Unsafe.Coerce
46+
47+import Data.Map (Map)
48+import qualified Data.Map as Map
49+
25type MonadReflexHost' t m = (MonadReflexHost t m, MonadIORef m, MonadIORef (HostFrame t))
50type MonadReflexHost' t m = (MonadReflexHost t m, MonadIORef m, MonadIORef (HostFrame t))
26
51
27
52
28−setupFiring :: (MonadReflexHost t m, MonadIORef m) => Plan t (Event t a) -> m (Ignore (EventHandle t a), Schedule t)
53+setupFiring :: (MonadReflexHost t m, MonadIORef m) => Plan t (Event t a) -> m (EventHandle t a, Schedule t)
29setupFiring p = do
54setupFiring p = do
30 (e, s) <- runPlan p
55 (e, s) <- runPlan p
31 h <- subscribeEvent e
56 h <- subscribeEvent e
32− return (Ignore h, s)
57+ return (h, s)
33
58
34-- Hack to avoid the NFData constraint for EventHandle which is a synonym
59-- Hack to avoid the NFData constraint for EventHandle which is a synonym
35newtype Ignore a = Ignore a
60newtype Ignore a = Ignore a
@@ -46,41 +71,74 @@ instance NFData (Firing t) where
46 rnf !(Firing _ _) = ()
71 rnf !(Firing _ _) = ()
47
72
48-- Measure the running time
73-- Measure the running time
49−benchFiring :: (MonadReflexHost' t m, MonadSample t m) => (forall a. m a -> IO a) -> (String, TestCase) -> Benchmark
74+benchFiring :: forall t m. (MonadReflexHost' t m, MonadSample t m) => (forall a. m a -> IO a) -> TestCase -> Int -> IO ()
50−benchFiring runHost (name, TestE p) = env setup (\e -> bench name $ whnfIO $ run e) where
75+benchFiring runHost tc n = runHost $ do
51− run (Ignore h, s) = runHost (readSchedule s (readEvent' h)) >> performGC
76+ let runIterations :: m a -> m ()
77+ runIterations test = replicateM_ (10*n) $ do
78+ result <- test
79+ liftIO $ evaluate result
80+ case tc of
81+ TestE p -> do
52− setup = runHost $ setupFiring p
82+ (h, s) <- setupFiring p
53−
83+ runIterations $ readSchedule s $ readEvent' h
54−benchFiring runHost (name, TestB p) = env setup (\e -> bench name $ whnfIO $ run e) where
84+ TestB p -> do
55− run (b, s) = runHost (readSchedule s (sample b)) >> performGC
56− setup = runHost $ do
57 (b, s) <- runPlan p
85 (b, s) <- runPlan p
58− return (b, makeDense s)
86+ runIterations $ readSchedule (makeDense s) $ sample b
87+
88+waitForFinalizers :: IO ()
89+waitForFinalizers = do
90+ performGC
91+ x <- getCurrentTime
92+ isFinalized <- newIORef False
93+ mkWeakPtr x $ Just $ writeIORef isFinalized True
94+ performGC
95+ fix $ \loop -> do
96+ f <- readIORef isFinalized
97+ if f then return () else do
98+ threadDelay 1
99+ loop
100+
101+benchmarks :: [(String, Int -> IO ())]
102+benchmarks = implGroup "spider" runSpiderHost cases
103+ where
104+ implGroup :: (MonadReflexHost' t m, MonadSample t m) => String -> (forall a. m a -> IO a) -> [(String, TestCase)] -> [(String, Int -> IO ())]
105+ implGroup name runHost = group name . fmap (second (benchFiring runHost))
106+ group name = fmap $ first ((name <> "/") <>)
107+ sub n frames = group ("subscribing " ++ show (n, frames)) $ Focused.subscribing n frames
108+ firing n = group ("firing " <> show n) $ Focused.firing n
109+ merging n = group ("merging " <> show n) $ Focused.merging n
110+ dynamics n = group ("dynamics " <> show n) $ Focused.dynamics n
111+ cases = concat
112+ [ sub 100 40
113+ , dynamics 100
114+ , dynamics 1000
115+ , firing 1000
116+ , firing 10000
117+ , merging 10
118+ , merging 50
119+ , merging 100
120+ , merging 200
121+ ]
122+
123+pattern RunTestCaseFlag = "--run-test-case"
124+
125+spawnBenchmark :: String -> Benchmark
126+spawnBenchmark name = Benchmark name $ Benchmarkable $ \n -> do
127+ self <- getExecutablePath
128+ callProcess self [RunTestCaseFlag, name, show n, "+RTS", "-N1"]
129+
130+foreign import ccall unsafe "myCapabilityHasOtherRunnableThreads" myCapabilityHasOtherRunnableThreads :: IO Bool
59
131
60main :: IO ()
132main :: IO ()
61main = do
133main = do
62− hSetBuffering stdout LineBuffering
134+ args <- getArgs
135+ case args of
136+ RunTestCaseFlag : t -> case t of
137+ [name, readMaybe -> Just count] -> do
138+ case lookup name benchmarks of
139+ Just testCase -> testCase count
140+ performGC
141+ fix $ \loop -> bool (return ()) (yield >> loop) =<< myCapabilityHasOtherRunnableThreads
142+ return ()
143+ _ -> $failure "--run-test-case: expected test name and iteration count to follow"
63− defaultMainWith (defaultConfig { timeLimit = 10, csvFile = Just "dmap-original.csv" })
144+ _ -> defaultMainWith (defaultConfig { timeLimit = 20, csvFile = Just "dmap-original.csv", reportFile = Just "report.html" }) $ fmap (spawnBenchmark . fst) benchmarks
64− [ benchImpl "spider" runSpiderHost
65− ]
66−
67−benchImpl :: (MonadReflexHost' t m, MonadSample t m) => String -> (forall a. m a -> IO a) -> Benchmark
68−benchImpl name runHost = bgroup name [ sub 100 40
69− , dynamics 100
70− , dynamics 1000
71− , firing 1000
72− , firing 10000
73− , merging 10
74− , merging 50
75− , merging 100
76− , merging 200]
77− where
78− sub n frames = runGroup ("subscribing " ++ show (n, frames)) $ Focused.subscribing n frames
79− firing n = runGroup ("firing " ++ show n) $ Focused.firing n
80− merging n = runGroup ("merging " ++ show n) $ Focused.merging n
81− dynamics n = runGroup ("dynamics " ++ show n) $ Focused.dynamics n
82−
83− runGroup name' benchmarks = bgroup name' (benchFiring runHost <$> benchmarks)
84−
85−
86−
default.nix+2−2
@@ -2,7 +2,7 @@
2, exception-transformers, haskell-src-exts, haskell-src-meta
2, exception-transformers, haskell-src-exts, haskell-src-meta
3, MemoTrie, mtl, primitive, ref-tf, semigroups, stdenv, syb
3, MemoTrie, mtl, primitive, ref-tf, semigroups, stdenv, syb
4, template-haskell, these, transformers, transformers-compat
4, template-haskell, these, transformers, transformers-compat
5−, criterion, deepseq, split, stm
5+, criterion, deepseq, split, stm, loch-th
6}:
6}:
7mkDerivation {
7mkDerivation {
8 pname = "reflex";
8 pname = "reflex";
@@ -16,7 +16,7 @@ mkDerivation {
16 testHaskellDepends = [
16 testHaskellDepends = [
17 base containers dependent-map MemoTrie mtl ref-tf
17 base containers dependent-map MemoTrie mtl ref-tf
18 # Benchmark dependencies
18 # Benchmark dependencies
19− criterion deepseq split stm
19+ criterion deepseq split stm loch-th
20 ];
20 ];
21 homepage = "https://github.com/reflex-frp/reflex";
21 homepage = "https://github.com/reflex-frp/reflex";
22 description = "Higher-order Functional Reactive Programming";
22 description = "Higher-order Functional Reactive Programming";
reflex.cabal+6−2
@@ -86,8 +86,9 @@ benchmark spider-bench
86benchmark saulzar-bench
86benchmark saulzar-bench
87 type: exitcode-stdio-1.0
87 type: exitcode-stdio-1.0
88 hs-source-dirs: bench test
88 hs-source-dirs: bench test
89+ c-sources: bench-cbits/checkCapability.c
89 main-is: RunAll.hs
90 main-is: RunAll.hs
90− ghc-options: -O2 -rtsopts
91+ ghc-options: -O2 -rtsopts -threaded
91 build-depends:
92 build-depends:
92 base,
93 base,
93 containers == 0.5.*,
94 containers == 0.5.*,
@@ -101,7 +102,10 @@ benchmark saulzar-bench
101 primitive,
102 primitive,
102 criterion == 1.1.*,
103 criterion == 1.1.*,
103 split,
104 split,
104− reflex
105+ reflex,
106+ time,
107+ loch-th,
108+ process
105
109
106source-repository head
110source-repository head
107 type: git
111 type: git
test/Reflex/Bench/Focused.hs+3−3
@@ -10,7 +10,7 @@ import Control.Monad
10import Control.Monad.Identity
10import Control.Monad.Identity
11import Control.Applicative
11import Control.Applicative
12--import Data.Foldable
12--import Data.Foldable
13−import Data.Traversable (traverse, for)
13+import Data.Traversable (for)
14
14
15import Data.Maybe
15import Data.Maybe
16import Data.Map (Map)
16import Data.Map (Map)
@@ -334,10 +334,10 @@ firing n =
334 , testB "pullChain" $ fmapChain n . current <$> (count =<< events 4)
334 , testB "pullChain" $ fmapChain n . current <$> (count =<< events 4)
335 , testB "pullChain2" $ do
335 , testB "pullChain2" $ do
336 es <- events 4
336 es <- events 4
337− e <- plan [(0, ())]
337+ e' <- plan [(0, ())]
338
338
339 b <- hold (constant 0) $
339 b <- hold (constant 0) $
340− pushAlways (const $ fmapChain n <$> hold 0 es) e
340+ pushAlways (const $ fmapChain n <$> hold 0 es) e'
341
341
342 return (join b)
342 return (join b)
343 , testB "counters/pullTree" $ mergeTree 8 <$> counters
343 , testB "counters/pullTree" $ mergeTree 8 <$> counters
test/Reflex/Plan/Reflex.hs+1−2
@@ -2,7 +2,7 @@
2module Reflex.Plan.Reflex
2module Reflex.Plan.Reflex
3 ( TestPlan(..)
3 ( TestPlan(..)
4 , runPlan
4 , runPlan
5− , Plan
5+ , Plan (..)
6 , Schedule
6 , Schedule
7 , Firing (..)
7 , Firing (..)
8 , MonadIORef
8 , MonadIORef
@@ -34,7 +34,6 @@ import Data.Maybe
34import qualified Data.IntMap.Strict as IntMap
34import qualified Data.IntMap.Strict as IntMap
35import Control.Monad.Ref
35import Control.Monad.Ref
36
36
37−
38import Data.IntMap.Strict (IntMap)
37import Data.IntMap.Strict (IntMap)
39import Data.IORef
38import Data.IORef
40import System.Mem
39import System.Mem