[GHC] #7606: Stride scheduling for Haskell threads with priorities

GHC cvs-ghc at haskell.org
Fri Jan 18 11:10:19 CET 2013


#7606: Stride scheduling for Haskell threads with priorities
-----------------------------+----------------------------------------------
Reporter:  ezyang            |          Owner:  ezyang          
    Type:  feature request   |         Status:  new             
Priority:  normal            |      Component:  Runtime System  
 Version:  7.7               |       Keywords:                  
      Os:  Unknown/Multiple  |   Architecture:  Unknown/Multiple
 Failure:  None/Unknown      |      Blockedby:                  
Blocking:                    |        Related:                  
-----------------------------+----------------------------------------------
 Currently, GHC uses a round-robin scheduler for Haskell threads, with some
 heuristics for when threads should be bumped to the front of the queue.
 This patch set replaces this scheduler with 'stride scheduling', as
 described by Waldspurger and Weihl '95, which is an efficient,
 deterministic method for scheduling processes with differing priorities.
 Priorities are assigned by giving 'tickets' to threads; a thread with
 twice as many tickets as another will run twice as often. I’d like to
 replace the round-robin scheduler completely with this scheduler.

 Here are nofib benchmarks comparing the old scheduler to the new:

 {{{
 --------------------------------------------------------------------------------
         Program           Size    Allocs   Runtime   Elapsed  TotalMem
 --------------------------------------------------------------------------------
             Min          -0.0%    -52.2%    -18.8%    -18.6%    -83.1%
             Max          +1.0%     +4.2%     +4.9%     +5.1%     +7.7%
  Geometric Mean          +0.1%     -2.8%     -0.9%     -0.9%     -2.9%
 }}}

 Here are some technical details:

  * Without any changes to ticket values, scheduling behavior should be
 functionally identical to round-robin. (By default, new threads, including
 the IO thread, get allocated the max nubmer of tickets possible.) This is
 not quite identical, since our heap does not have FIFO property (see
 below.)

  * The current patch-set uses a very simple (e.g. undergrad level)
 resizable-array backed heap to implement the priority queue; we can play
 some tricks to reduce the memory footprint of the priority queue (e.g.
 using the container_of macro to eliminate the extra keys store); and a
 more fancy data structure would make it easier for us to surgically remove
 entries or reweight them, but I wanted to keep overhead low. If anyone has
 a pet implementation of priority queues in C feel free to swap it in.
 Right now, this only affects the uses of promoteInRunQueue() in
 Messages.c; I still need to check if #3838 has regressed.

  * We get three new primops: `setTickets#`, `getTickets#` and
 `modifyTickets#`. We don't support creating threads with specific numbers
 of tickets (mostly because that would have added an annoyingly large set
 of extra primops); instead, you're expected to spawn a thread which gets
 max-ticket allocation, and then weight it down.

  * `_link` is no longer used for linking TSOs in the run queue. I have
 tried my best to stamp out any code which operated on this assumption, but
 I may have missed some.

  * Modifying a TSO's tickets takes out the scheduler lock; the hope is
 that this operation is quick and rare enough that a global lock here is
 not too bad.

  * We are unfortunately stuck with some magic constants w.r.t. ticket
 values: 1 << 20 is the maximum number of tickets our implementation is
 hard-coded to support.

  * Sleeping and blocked tasks do not get any 'bonus' for being blocked.

  * In an ideal world, when a thread hits a black hole, it should
 temporarily give its tickets to the thread evaluating the black hole, so
 it will unblock more quickly.  Unfortunately, implementing this is pretty
 complicated (the blackhole evaluating thread could die; or it could get
 stuck on a blackhole itself and need to gift its tickets; it shouldn't be
 able to give away the tickets it was gifted.) So this implementation
 leaves that out. Similar semantics for MVars are probably possible, but
 will require userland assistance too.

 I haven't prepared a patch to base yet with a user-level API, but here is
 a proposed draft:

 {{{
 type Tickets = Int

 -- | Maximum number of tickets we support a thread having. (Currently 2 >>
 20)
 -- Note that this doesn't bound the *global* maximum tickets.
 maxTickets :: Tickets

 -- | Changes the number of tickets allocated to a thread.  The ticket
 count must
 -- not be less than or equal to zero, or greater than maxTickets.
 (Corresponds
 -- to setTickets# primop)
 setTickets :: ThreadId -> Tickets -> IO ()

 -- | Returns the number of tickets currently allocated to a thread.
 (Corresponds to
 -- getTickets# primop)
 getTickets :: ThreadId -> IO Tickets

 -- | Atomically performs a linear transformation on the number of tickets
 a thread;
 -- e.g. scaling the number of tickets by a rational number, adding another
 fixed
 -- set of tickets, and then returning the number of 'leftover' tickets
 from the operation; e.g.
 -- if the net amount of tickets is reduced, then the returned result is
 positive;
 -- if the net amount of tickets is increased, the returned result is
 negative.
 -- In the absence of concurrent threads, the following property holds
 forall
 -- t, m and x:
 --
 --      do r <- getTickets t
 --         d <- scaleTickets t m x
 --         r' <- getTickets t
 --         return (r == r' + d)
 --
 -- If the scaling would reduce the number of tickets below zero or
 increase the
 -- number of tickets beyond maxTickets, this function will instead fail
 with
 -- an exception.  This function will be subject to some rounding error;
 powers of two
 -- are, however, likely to be exact.  (Corresponds to modifyTickets#
 primop; note
 -- that the sentinel value for failure is maxTickets + 1, since it is
 impossible for
 -- a thread's ticket value to change by that much.)
 modifyTickets :: ThreadId -> Ratio Int -> Tickets -> IO Tickets

 -- | Forks a new thread, transferring some percentage of tickets from the
 current
 -- thread to it (so the net number of tickets stays constant.)  Fails if
 the rational
 -- is greater than 1 or less than or equal to zero, or if there are not
 enough tickets
 -- in the current thread.
 forkIOWith :: IO a -> Ratio Int -> IO ThreadId

 }}}

-- 
Ticket URL: <http://hackage.haskell.org/trac/ghc/ticket/7606>
GHC <http://www.haskell.org/ghc/>
The Glasgow Haskell Compiler



More information about the ghc-tickets mailing list