Skip to content

Commit

Permalink
move some code around
Browse files Browse the repository at this point in the history
  • Loading branch information
mitchellwrosen committed Oct 1, 2023
1 parent 7fc6a84 commit 450e6ab
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 121 deletions.
36 changes: 18 additions & 18 deletions src/TimerWheel.hs
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ import qualified TimerWheel.Internal.Nanoseconds as Nanoseconds
import TimerWheel.Internal.Prelude
import TimerWheel.Internal.Timestamp (Timestamp)
import qualified TimerWheel.Internal.Timestamp as Timestamp
import TimerWheel.Internal.TimestampMap (TimestampMap)
import qualified TimerWheel.Internal.TimestampMap as TimestampMap
import TimerWheel.Internal.WordMap (WordMap)
import qualified TimerWheel.Internal.WordMap as WordMap

-- | A timer wheel is a vector-of-collections-of timers to fire. Timers may be one-shot or recurring, and may be
-- scheduled arbitrarily far in the future.
Expand Down Expand Up @@ -126,7 +126,7 @@ create ::
-- |
IO TimerWheel
create scope (Config spokes0 resolution0) = do
buckets <- Array.newArray spokes TimestampMap.empty
buckets <- Array.newArray spokes WordMap.empty
numTimers <- newCounter
timerIdSupply <- newCounter
Ki.fork_ scope (runTimerReaperThread buckets numTimers resolution)
Expand Down Expand Up @@ -290,14 +290,14 @@ timestampToIndex buckets resolution timestamp =
-- Timer bucket operations

type TimerBucket =
TimestampMap Timers
WordMap Timers

timerBucketDelete :: Timestamp -> TimerId -> TimerBucket -> Maybe TimerBucket
timerBucketDelete timestamp timerId bucket =
case TimestampMap.lookup timestamp bucket of
timerBucketDelete (coerce @Timestamp @Word64 -> timestamp) timerId bucket =
case WordMap.lookup timestamp bucket of
Nothing -> Nothing
Just (Timers1 timer)
| timerId == getTimerId timer -> Just $! TimestampMap.delete timestamp bucket
| timerId == getTimerId timer -> Just $! WordMap.delete timestamp bucket
| otherwise -> Nothing
Just (TimersN timers0) ->
case timersDelete timerId timers0 of
Expand All @@ -307,11 +307,11 @@ timerBucketDelete timestamp timerId bucket =
case timers1 of
[timer] -> Timers1 timer
_ -> TimersN timers1
in Just $! TimestampMap.insert timestamp timers2 bucket
in Just $! WordMap.insert timestamp timers2 bucket

timerBucketInsert :: Timestamp -> Timer0 -> TimerBucket -> TimerBucket
timerBucketInsert timestamp timer =
TimestampMap.upsert timestamp (Timers1 timer) \case
WordMap.upsert (coerce @Timestamp @Word64 timestamp) (Timers1 timer) \case
Timers1 old -> TimersN [timer, old]
TimersN old -> TimersN (timer : old)

Expand Down Expand Up @@ -372,15 +372,15 @@ atomicMaybeModifyArray buckets index doDelete = do
if success then pure True else loop ticket1

atomicExtractExpiredTimersFromBucket :: MutableArray RealWorld TimerBucket -> Int -> Timestamp -> IO TimerBucket
atomicExtractExpiredTimersFromBucket buckets index now = do
atomicExtractExpiredTimersFromBucket buckets index (coerce @Timestamp @Word64 -> now) = do
ticket0 <- Atomics.readArrayElem buckets index
loop ticket0
where
loop :: Atomics.Ticket TimerBucket -> IO TimerBucket
loop ticket = do
let Pair expired bucket1 = TimestampMap.splitL now (Atomics.peekTicket ticket)
if TimestampMap.null expired
then pure TimestampMap.empty
let WordMap.Pair expired bucket1 = WordMap.splitL now (Atomics.peekTicket ticket)
if WordMap.null expired
then pure WordMap.empty
else do
(success, ticket1) <- Atomics.casArrayElem buckets index ticket bucket1
if success then pure expired else loop ticket1
Expand Down Expand Up @@ -515,15 +515,15 @@ runTimerReaperThread buckets numTimers resolution = do
where
fireTimerBucket :: TimerBucket -> IO ()
fireTimerBucket bucket0 =
case TimestampMap.pop bucket0 of
TimestampMap.PopNada -> pure ()
TimestampMap.PopAlgo timestamp timers bucket1 ->
case WordMap.pop bucket0 of
WordMap.PopNada -> pure ()
WordMap.PopAlgo timestamp timers bucket1 ->
case timers of
Timers1 timer -> do
expired2 <- fireTimer bucket1 timestamp timer
expired2 <- fireTimer bucket1 (coerce @Word64 @Timestamp timestamp) timer
fireTimerBucket expired2
TimersN timers1 -> do
bucket2 <- fireTimers bucket1 timestamp timers1
bucket2 <- fireTimers bucket1 (coerce @Word64 @Timestamp timestamp) timers1
fireTimerBucket bucket2

fireTimers :: TimerBucket -> Timestamp -> [Timer0] -> IO TimerBucket
Expand Down
17 changes: 1 addition & 16 deletions src/TimerWheel/Internal/Prelude.hs
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
module TimerWheel.Internal.Prelude
( Pair (..),
mapPairL,
mapPairR,
Seconds,
( Seconds,
module X,
)
where
Expand All @@ -15,18 +12,6 @@ import Data.Word as X (Word64)
import GHC.Generics as X (Generic)
import Prelude as X hiding (lookup, null)

-- A strict pair
data Pair a b
= Pair !a !b

mapPairL :: (a -> b) -> Pair a x -> Pair b x
mapPairL f (Pair x y) = Pair (f x) y
{-# INLINE mapPairL #-}

mapPairR :: (a -> b) -> Pair x a -> Pair x b
mapPairR f (Pair x y) = Pair x (f y)
{-# INLINE mapPairR #-}

-- | A number of seconds, with nanosecond precision.
--
-- You can use numeric literals to construct a value of this type, e.g. @0.5@.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,149 +1,124 @@
-- A Word64Map with code cribbed from containers and GHC
-- An IntMap-like container with code cribbed from containers and GHC
--
-- TODO: proper code attribution
module TimerWheel.Internal.TimestampMap
( TimestampMap,
module TimerWheel.Internal.WordMap
( WordMap (..),
delete,
empty,
foreach,
insert,
lookup,
null,
Pop (..),
pop,
splitL,
upsert,

-- * Strict pair
Pair (..),
)
where

import Data.Bits
import Data.Word
import TimerWheel.Internal.Nanoseconds (Nanoseconds (..))
import TimerWheel.Internal.Prelude
import TimerWheel.Internal.Timestamp (Timestamp (..))

data TimestampMap a
= Bin
{-# UNPACK #-} !Prefix
{-# UNPACK #-} !Mask
!(TimestampMap a)
!(TimestampMap a)

data WordMap a
= Bin {-# UNPACK #-} !Prefix {-# UNPACK #-} !Mask !(WordMap1 a) !(WordMap1 a)
| Tip {-# UNPACK #-} !Word64 a
| Nil

-- A non-empty word map.
type WordMap1 a = WordMap a

type Prefix = Word64

type Mask = Word64

bin :: Prefix -> Mask -> TimestampMap a -> TimestampMap a -> TimestampMap a
bin :: Prefix -> Mask -> WordMap a -> WordMap a -> WordMap a
bin _ _ l Nil = l
bin _ _ Nil r = r
bin p m l r = Bin p m l r
{-# INLINE bin #-}

binCheckLeft :: Prefix -> Mask -> TimestampMap a -> TimestampMap a -> TimestampMap a
binCheckLeft :: Prefix -> Mask -> WordMap a -> WordMap1 a -> WordMap1 a
binCheckLeft _ _ Nil r = r
binCheckLeft p m l r = Bin p m l r
{-# INLINE binCheckLeft #-}

binCheckRight :: Prefix -> Mask -> TimestampMap a -> TimestampMap a -> TimestampMap a
binCheckRight :: Prefix -> Mask -> WordMap1 a -> WordMap a -> WordMap1 a
binCheckRight _ _ l Nil = l
binCheckRight p m l r = Bin p m l r
{-# INLINE binCheckRight #-}

delete :: forall a. Timestamp -> TimestampMap a -> TimestampMap a
delete = coerce @(Word64 -> TimestampMap a -> TimestampMap a) delete_
{-# INLINE delete #-}

delete_ :: Word64 -> TimestampMap a -> TimestampMap a
delete_ !k = \case
t@(Bin p m l r)
| nomatch k p m -> t
| zero k m -> binCheckLeft p m (delete_ k l) r
| otherwise -> binCheckRight p m l (delete_ k r)
t@(Tip kx _)
| k == kx -> Nil
| otherwise -> t
Nil -> Nil

empty :: TimestampMap a
delete :: Word64 -> WordMap a -> WordMap a
delete !k t =
case t of
Bin p m l r
| nomatch k p m -> t
| zero k m -> binCheckLeft p m (delete k l) r
| otherwise -> binCheckRight p m l (delete k r)
Tip kx _
| k == kx -> Nil
| otherwise -> t
Nil -> Nil

empty :: WordMap a
empty =
Nil
{-# INLINE empty #-}

foreach :: (Timestamp -> a -> IO ()) -> TimestampMap a -> IO ()
foreach f =
go
where
go = \case
Nil -> pure ()
Tip k x -> f (coerce @Word64 @Timestamp k) x
Bin _ _ l r -> go l >> go r

insert :: forall a. Timestamp -> a -> TimestampMap a -> TimestampMap a
insert = coerce @(Word64 -> a -> TimestampMap a -> TimestampMap a) insert_
{-# INLINE insert #-}

insert_ :: Word64 -> a -> TimestampMap a -> TimestampMap a
insert_ !k !x t =
insert :: Word64 -> a -> WordMap a -> WordMap a
insert !k !x t =
case t of
Bin p m l r
| nomatch k p m -> link k (Tip k x) p t
| zero k m -> Bin p m (insert_ k x l) r
| otherwise -> Bin p m l (insert_ k x r)
| zero k m -> Bin p m (insert k x l) r
| otherwise -> Bin p m l (insert k x r)
Tip ky _
| k == ky -> Tip k x
| otherwise -> link k (Tip k x) ky t
Nil -> Tip k x

link :: Prefix -> TimestampMap a -> Prefix -> TimestampMap a -> TimestampMap a
link :: Prefix -> WordMap a -> Prefix -> WordMap a -> WordMap a
link p1 t1 p2 = linkWithMask (branchMask p1 p2) p1 t1
{-# INLINE link #-}

-- `linkWithMask` is useful when the `branchMask` has already been computed
linkWithMask :: Mask -> Prefix -> TimestampMap a -> TimestampMap a -> TimestampMap a
linkWithMask :: Mask -> Prefix -> WordMap a -> WordMap a -> WordMap a
linkWithMask m p0 t1 t2
| zero p0 m = Bin p m t1 t2
| otherwise = Bin p m t2 t1
where
p = mask p0 m
{-# INLINE linkWithMask #-}

lookup :: forall a. Timestamp -> TimestampMap a -> Maybe a
lookup = coerce @(Word64 -> TimestampMap a -> Maybe a) lookup_
{-# INLINE lookup #-}

lookup_ :: forall a. Word64 -> TimestampMap a -> Maybe a
lookup_ !k =
lookup :: forall a. Word64 -> WordMap a -> Maybe a
lookup !k =
go
where
go :: TimestampMap a -> Maybe a
go :: WordMap a -> Maybe a
go = \case
Bin _ m l r -> go (if zero k m then l else r)
Tip kx x -> if k == kx then Just x else Nothing
Nil -> Nothing

null :: TimestampMap a -> Bool
null :: WordMap a -> Bool
null = \case
Nil -> True
_ -> False
{-# INLINE null #-}

singleton :: Word64 -> a -> TimestampMap a
singleton :: Word64 -> a -> WordMap a
singleton k !x = Tip k x
{-# INLINE singleton #-}

-- @splitL k xs@ splits @xs@ into @ys@ and @zs@, where every timestamp in @ys@ is less than or equal to @k@, and every
-- timestamp in @zs@ is greater than @k@.
splitL :: forall a. Timestamp -> TimestampMap a -> Pair (TimestampMap a) (TimestampMap a)
splitL = coerce @(Word64 -> TimestampMap a -> Pair (TimestampMap a) (TimestampMap a)) splitL_
{-# INLINE splitL #-}

splitL_ :: forall a. Word64 -> TimestampMap a -> Pair (TimestampMap a) (TimestampMap a)
splitL_ k = \case
Bin p m l r | m < 0 -> mapPairL (\ll -> bin p m ll r) (go l)
t -> go t
-- @splitL k xs@ splits @xs@ into @ys@ and @zs@, where every value in @ys@ is less than or equal to @k@, and every value
-- in @zs@ is greater than @k@.
splitL :: forall a. Word64 -> WordMap a -> Pair (WordMap a) (WordMap a)
splitL k =
go
where
go :: TimestampMap a -> Pair (TimestampMap a) (TimestampMap a)
go :: WordMap a -> Pair (WordMap a) (WordMap a)
go = \case
t@(Bin p m l r)
| nomatch k p m ->
Expand All @@ -159,9 +134,9 @@ splitL_ k = \case

data Pop a
= PopNada
| PopAlgo !Timestamp !a !(TimestampMap a)
| PopAlgo !Word64 !a !(WordMap a)

pop :: TimestampMap a -> Pop a
pop :: WordMap a -> Pop a
pop = \case
Nil -> PopNada
Bin p m l r0 | m < 0 ->
Expand All @@ -171,25 +146,21 @@ pop = \case
t -> pop1 t
{-# INLINE pop #-}

pop1 :: TimestampMap a -> Pop a
pop1 :: WordMap1 a -> Pop a
pop1 = \case
Bin p m l0 r ->
case pop1 l0 of
PopAlgo k x l1 -> PopAlgo k x (binCheckLeft p m l1 r)
PopNada -> undefined
Tip k x -> PopAlgo (coerce @Word64 @Timestamp k) x Nil
Tip k x -> PopAlgo k x Nil
Nil -> undefined

upsert :: forall a. Timestamp -> a -> (a -> a) -> TimestampMap a -> TimestampMap a
upsert = coerce @(Word64 -> a -> (a -> a) -> TimestampMap a -> TimestampMap a) upsert_
{-# INLINE upsert #-}

upsert_ :: Word64 -> a -> (a -> a) -> TimestampMap a -> TimestampMap a
upsert_ !k x f = \case
upsert :: Word64 -> a -> (a -> a) -> WordMap a -> WordMap a
upsert !k x f = \case
t@(Bin p m l r)
| nomatch k p m -> link k (singleton k x) p t
| zero k m -> Bin p m (upsert_ k x f l) r
| otherwise -> Bin p m l (upsert_ k x f r)
| zero k m -> Bin p m (upsert k x f l) r
| otherwise -> Bin p m l (upsert k x f r)
t@(Tip ky y)
| k == ky -> Tip k $! f y
| otherwise -> link k (singleton k x) ky t
Expand Down Expand Up @@ -227,3 +198,15 @@ branchMask p1 p2 =
highestBitMask :: Word64 -> Word64
highestBitMask w = unsafeShiftL 1 (63 - countLeadingZeros w)
{-# INLINE highestBitMask #-}

-- A strict pair
data Pair a b
= Pair !a !b

mapPairL :: (a -> b) -> Pair a x -> Pair b x
mapPairL f (Pair x y) = Pair (f x) y
{-# INLINE mapPairL #-}

mapPairR :: (a -> b) -> Pair x a -> Pair x b
mapPairR f (Pair x y) = Pair x (f y)
{-# INLINE mapPairR #-}
File renamed without changes.
Loading

0 comments on commit 450e6ab

Please sign in to comment.