on cedc81c help sign in
616 lines 20 kB history raw
1-- |
2-- Module : Streamly.Internal.Data.Ring
3-- Copyright : (c) 2019 Composewell Technologies
4-- License : BSD3
5-- Maintainer : streamly@composewell.com
6-- Stability : experimental
7-- Portability : GHC
8--
9-- A ring array is a circular mutable array.
11-- XXX Write benchmarks
12-- XXX Make the implementation similar to mutable array
13-- XXX Rename this module to Data.RingArray.Storable
15module Streamly.Internal.Data.Ring
16 ( Ring(..)
18 -- * Construction
19 , new
20 , newRing
21 , writeN
23 , advance
24 , moveBy
25 , startOf
27 -- * Random writes
28 , unsafeInsert
29 , slide
30 , putIndex
31 , modifyIndex
33 -- * Unfolds
34 , read
35 , readRev
37 -- * Random reads
38 , getIndex
39 , getIndexUnsafe
40 , getIndexRev
42 -- * Size
43 , length
44 , byteLength
45 -- , capacity
46 , byteCapacity
47 , bytesFree
49 -- * Casting
50 , cast
51 , castUnsafe
52 , asBytes
53 , fromArray
55 -- * Folds
56 , unsafeFoldRing
57 , unsafeFoldRingM
58 , unsafeFoldRingFullM
59 , unsafeFoldRingNM
61 -- * Stream of Arrays
62 , ringsOf
64 -- * Fast Byte Comparisons
65 , unsafeEqArray
66 , unsafeEqArrayN
68 , slidingWindow
69 , slidingWindowWith
70 ) where
72#include "ArrayMacros.h"
73#include "inline.hs"
75import Control.Exception (assert)
76import Control.Monad.IO.Class (MonadIO(..))
77import Data.Word (Word8)
78import Foreign.Storable
79import Foreign.ForeignPtr (ForeignPtr, withForeignPtr, touchForeignPtr)
80import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
81import Foreign.Ptr (plusPtr, minusPtr, castPtr)
82import Streamly.Internal.Data.Unbox as Unboxed (Unbox(peekByteIndex))
83import GHC.ForeignPtr (mallocPlainForeignPtrAlignedBytes)
84import GHC.Ptr (Ptr(..))
85import Streamly.Internal.Data.MutArray.Type (MutArray)
86import Streamly.Internal.Data.Fold.Type (Fold(..), Step(..), lmap)
87import Streamly.Internal.Data.Stream.Type (Stream)
88import Streamly.Internal.Data.Stream.Step (Step(..))
89import Streamly.Internal.Data.Unfold.Type (Unfold(..))
90import Streamly.Internal.System.IO (unsafeInlineIO)
92import qualified Streamly.Internal.Data.MutArray.Type as MA
93import qualified Streamly.Internal.Data.Array.Type as A
95import Prelude hiding (length, concat, read)
97-- $setup
98-- >>> :m
99-- >>> import qualified Streamly.Internal.Data.Ring as Ring
101-- | A ring buffer is a mutable array of fixed size. Initially the array is
102-- empty, with ringStart pointing at the start of allocated memory. We call the
103-- next location to be written in the ring as ringHead. Initially ringHead ==
104-- ringStart. When the first item is added, ringHead points to ringStart +
105-- sizeof item. When the buffer becomes full ringHead would wrap around to
106-- ringStart. When the buffer is full, ringHead always points at the oldest
107-- item in the ring and the newest item added always overwrites the oldest
108-- item.
109--
110-- When using it we should keep in mind that a ringBuffer is a mutable data
111-- structure. We should not leak out references to it for immutable use.
112--
113data Ring a = Ring
114 { ringStart :: {-# UNPACK #-} !(ForeignPtr a) -- first address
115 , ringBound :: {-# UNPACK #-} !(Ptr a) -- first address beyond allocated memory
116 }
118-------------------------------------------------------------------------------
119-- Construction
120-------------------------------------------------------------------------------
122-- | Get the first address of the ring as a pointer.
123startOf :: Ring a -> Ptr a
124startOf = unsafeForeignPtrToPtr . ringStart
126-- | Create a new ringbuffer and return the ring buffer and the ringHead.
127-- Returns the ring and the ringHead, the ringHead is same as ringStart.
128{-# INLINE new #-}
129new :: forall a. Storable a => Int -> IO (Ring a, Ptr a)
130new count = do
131 let size = count * max 1 (sizeOf (undefined :: a))
132 fptr <- mallocPlainForeignPtrAlignedBytes size (alignment (undefined :: a))
133 let p = unsafeForeignPtrToPtr fptr
134 return (Ring
135 { ringStart = fptr
136 , ringBound = p `plusPtr` size
137 }, p)
139-- XXX Rename this to "new".
140--
141-- | @newRing count@ allocates an empty array that can hold 'count' items. The
142-- memory of the array is uninitialized and the allocation is aligned as per
143-- the 'Storable' instance of the type.
144--
145-- /Unimplemented/
146{-# INLINE newRing #-}
147newRing :: Int -> m (Ring a)
148newRing = undefined
150-- | Advance the ringHead by 1 item, wrap around if we hit the end of the
151-- array.
152{-# INLINE advance #-}
153advance :: forall a. Storable a => Ring a -> Ptr a -> Ptr a
154advance Ring{..} ringHead =
155 let ptr = PTR_NEXT(ringHead,a)
156 in if ptr < ringBound
157 then ptr
158 else unsafeForeignPtrToPtr ringStart
160-- | Move the ringHead by n items. The direction depends on the sign on whether
161-- n is positive or negative. Wrap around if we hit the beginning or end of the
162-- array.
163{-# INLINE moveBy #-}
164moveBy :: forall a. Storable a => Int -> Ring a -> Ptr a -> Ptr a
165moveBy by Ring {..} ringHead = ringStartPtr `plusPtr` advanceFromHead
167 where
169 elemSize = STORABLE_SIZE_OF(a)
170 ringStartPtr = unsafeForeignPtrToPtr ringStart
171 lenInBytes = ringBound `minusPtr` ringStartPtr
172 offInBytes = ringHead `minusPtr` ringStartPtr
173 len = assert (lenInBytes `mod` elemSize == 0) $ lenInBytes `div` elemSize
174 off = assert (offInBytes `mod` elemSize == 0) $ offInBytes `div` elemSize
175 advanceFromHead = (off + by `mod` len) * elemSize
177-- XXX Move the writeLastN from array module here.
178--
179-- | @writeN n@ is a rolling fold that keeps the last n elements of the stream
180-- in a ring array.
181--
182-- /Unimplemented/
183{-# INLINE writeN #-}
184writeN :: -- (Storable a, MonadIO m) =>
185 Int -> Fold m a (Ring a)
186writeN = undefined
188-------------------------------------------------------------------------------
189-- Conversions
190-------------------------------------------------------------------------------
192-- | Cast a mutable array to a ring array.
193fromArray :: MutArray a -> Ring a
194fromArray = undefined
196-------------------------------------------------------------------------------
197-- Conversion to/from array
198-------------------------------------------------------------------------------
200-- | Modify a given index of a ring array using a modifier function.
201--
202-- /Unimplemented/
203modifyIndex :: -- forall m a b. (MonadIO m, Storable a) =>
204 Ring a -> Int -> (a -> (a, b)) -> m b
205modifyIndex = undefined
207-- | /O(1)/ Write the given element at the given index in the ring array.
208-- Performs in-place mutation of the array.
209--
210-- >>> putIndex arr ix val = Ring.modifyIndex arr ix (const (val, ()))
211--
212-- /Unimplemented/
213{-# INLINE putIndex #-}
214putIndex :: -- (MonadIO m, Storable a) =>
215 Ring a -> Int -> a -> m ()
216putIndex = undefined
218-- | Insert an item at the head of the ring, when the ring is full this
219-- replaces the oldest item in the ring with the new item. This is unsafe
220-- beause ringHead supplied is not verified to be within the Ring. Also,
221-- the ringStart foreignPtr must be guaranteed to be alive by the caller.
222{-# INLINE unsafeInsert #-}
223unsafeInsert :: Storable a => Ring a -> Ptr a -> a -> IO (Ptr a)
224unsafeInsert rb ringHead newVal = do
225 poke ringHead newVal
226 -- touchForeignPtr (ringStart rb)
227 return $ advance rb ringHead
229-- | Insert an item at the head of the ring, when the ring is full this
230-- replaces the oldest item in the ring with the new item.
231--
232-- /Unimplemented/
233slide :: -- forall m a. (MonadIO m, Storable a) =>
234 Ring a -> a -> m (Ring a)
235slide = undefined
237-------------------------------------------------------------------------------
238-- Random reads
239-------------------------------------------------------------------------------
241-- | Return the element at the specified index without checking the bounds.
242--
243-- Unsafe because it does not check the bounds of the ring array.
244{-# INLINE_NORMAL getIndexUnsafe #-}
245getIndexUnsafe :: -- forall m a. (MonadIO m, Storable a) =>
246 Ring a -> Int -> m a
247getIndexUnsafe = undefined
249-- | /O(1)/ Lookup the element at the given index. Index starts from 0.
250--
251{-# INLINE getIndex #-}
252getIndex :: -- (MonadIO m, Storable a) =>
253 Ring a -> Int -> m a
254getIndex = undefined
256-- | /O(1)/ Lookup the element at the given index from the end of the array.
257-- Index starts from 0.
258--
259-- Slightly faster than computing the forward index and using getIndex.
260--
261{-# INLINE getIndexRev #-}
262getIndexRev :: -- (MonadIO m, Storable a) =>
263 Ring a -> Int -> m a
264getIndexRev = undefined
266-------------------------------------------------------------------------------
267-- Size
268-------------------------------------------------------------------------------
270-- | /O(1)/ Get the byte length of the array.
271--
272-- /Unimplemented/
273{-# INLINE byteLength #-}
274byteLength :: Ring a -> Int
275byteLength = undefined
277-- | /O(1)/ Get the length of the array i.e. the number of elements in the
278-- array.
279--
280-- Note that 'byteLength' is less expensive than this operation, as 'length'
281-- involves a costly division operation.
282--
283-- /Unimplemented/
284{-# INLINE length #-}
285length :: -- forall a. Storable a =>
286 Ring a -> Int
287length = undefined
289-- | Get the total capacity of an array. An array may have space reserved
290-- beyond the current used length of the array.
291--
292-- /Pre-release/
293{-# INLINE byteCapacity #-}
294byteCapacity :: Ring a -> Int
295byteCapacity = undefined
297-- | The remaining capacity in the array for appending more elements without
298-- reallocation.
299--
300-- /Pre-release/
301{-# INLINE bytesFree #-}
302bytesFree :: Ring a -> Int
303bytesFree = undefined
305-------------------------------------------------------------------------------
306-- Unfolds
307-------------------------------------------------------------------------------
309-- XXX We can read the ring in a loop and use "take" to restrict the number of
310-- elements to be taken.
311--
312-- | Read n elements from the ring starting at the supplied ring head. If n is
313-- more than the ring size it keeps reading the ring in a circular fashion.
314--
315-- If the ring is not full the user must ensure than n is less than or equal to
316-- the number of valid elements in the ring.
317--
318-- /Internal/
319{-# INLINE_NORMAL read #-}
320read :: forall m a. (MonadIO m, Storable a) => Unfold m (Ring a, Ptr a, Int) a
321read = Unfold step return
323 where
325 step (rb, rh, n) = do
326 if n <= 0
327 then do
328 liftIO $ touchForeignPtr (ringStart rb)
329 return Stop
330 else do
331 x <- liftIO $ peek rh
332 let rh1 = advance rb rh
333 return $ Yield x (rb, rh1, n - 1)
335-- | Unfold a ring array into a stream in reverse order.
336--
337-- /Unimplemented/
338{-# INLINE_NORMAL readRev #-}
339readRev :: -- forall m a. (MonadIO m, Storable a) =>
340 Unfold m (MutArray a) a
341readRev = undefined
343-------------------------------------------------------------------------------
344-- Stream of arrays
345-------------------------------------------------------------------------------
347-- XXX Move this module to a lower level Ring/Type module and move ringsOf to a
348-- higher level ring module where we can import "scan".
350-- | @ringsOf n stream@ groups the input stream into a stream of
351-- ring arrays of size n. Each ring is a sliding window of size n.
352--
353-- /Unimplemented/
354{-# INLINE_NORMAL ringsOf #-}
355ringsOf :: -- forall m a. (MonadIO m, Storable a) =>
356 Int -> Stream m a -> Stream m (MutArray a)
357ringsOf = undefined -- Stream.scan (writeN n)
359-------------------------------------------------------------------------------
360-- Casting
361-------------------------------------------------------------------------------
363-- | Cast an array having elements of type @a@ into an array having elements of
364-- type @b@. The array size must be a multiple of the size of type @b@.
365--
366-- /Unimplemented/
367--
368castUnsafe :: Ring a -> Ring b
369castUnsafe = undefined
371-- | Cast an @Array a@ into an @Array Word8@.
372--
373-- /Unimplemented/
374--
375asBytes :: Ring a -> Ring Word8
376asBytes = castUnsafe
378-- | Cast an array having elements of type @a@ into an array having elements of
379-- type @b@. The length of the array should be a multiple of the size of the
380-- target element otherwise 'Nothing' is returned.
381--
382-- /Pre-release/
383--
384cast :: forall a b. Storable b => Ring a -> Maybe (Ring b)
385cast arr =
386 let len = byteLength arr
387 r = len `mod` STORABLE_SIZE_OF(b)
388 in if r /= 0
389 then Nothing
390 else Just $ castUnsafe arr
392-------------------------------------------------------------------------------
393-- Equality
394-------------------------------------------------------------------------------
396-- XXX remove all usage of unsafeInlineIO
397--
398-- | Like 'unsafeEqArray' but compares only N bytes instead of entire length of
399-- the ring buffer. This is unsafe because the ringHead Ptr is not checked to
400-- be in range.
401{-# INLINE unsafeEqArrayN #-}
402unsafeEqArrayN :: Ring a -> Ptr a -> A.Array a -> Int -> Bool
403unsafeEqArrayN Ring{..} rh A.Array{..} nBytes
404 | nBytes < 0 = error "unsafeEqArrayN: n should be >= 0"
405 | nBytes == 0 = True
406 | otherwise = unsafeInlineIO $ check (castPtr rh) 0
408 where
410 w8Contents = arrContents
412 check p i = do
413 (relem :: Word8) <- peek p
414 aelem <- peekByteIndex i w8Contents
415 if relem == aelem
416 then go (p `plusPtr` 1) (i + 1)
417 else return False
419 go p i
420 | i == nBytes = return True
421 | castPtr p == ringBound =
422 go (castPtr (unsafeForeignPtrToPtr ringStart)) i
423 | castPtr p == rh = touchForeignPtr ringStart >> return True
424 | otherwise = check p i
426-- XXX This is not modular. We should probably just convert the array and the
427-- ring buffer to streams and compare the two streams. Need to check perf
428-- though.
430-- | Byte compare the entire length of ringBuffer with the given array,
431-- starting at the supplied ringHead pointer. Returns true if the Array and
432-- the ringBuffer have identical contents.
433--
434-- This is unsafe because the ringHead Ptr is not checked to be in range. The
435-- supplied array must be equal to or bigger than the ringBuffer, ARRAY BOUNDS
436-- ARE NOT CHECKED.
437{-# INLINE unsafeEqArray #-}
438unsafeEqArray :: Ring a -> Ptr a -> A.Array a -> Bool
439unsafeEqArray Ring{..} rh A.Array{..} =
440 unsafeInlineIO $ check (castPtr rh) 0
442 where
444 w8Contents = arrContents
446 check p i = do
447 (relem :: Word8) <- peek p
448 aelem <- peekByteIndex i w8Contents
449 if relem == aelem
450 then go (p `plusPtr` 1) (i + 1)
451 else return False
453 go p i
454 | castPtr p ==
455 ringBound = go (castPtr (unsafeForeignPtrToPtr ringStart)) i
456 | castPtr p == rh = touchForeignPtr ringStart >> return True
457 | otherwise = check p i
459-------------------------------------------------------------------------------
460-- Folding
461-------------------------------------------------------------------------------
463-- XXX We can unfold it into a stream and fold the stream instead.
464-- XXX use MonadIO
465--
466-- | Fold the buffer starting from ringStart up to the given 'Ptr' using a pure
467-- step function. This is useful to fold the items in the ring when the ring is
468-- not full. The supplied pointer is usually the end of the ring.
469--
470-- Unsafe because the supplied Ptr is not checked to be in range.
471{-# INLINE unsafeFoldRing #-}
472unsafeFoldRing :: forall a b. Storable a
473 => Ptr a -> (b -> a -> b) -> b -> Ring a -> b
474unsafeFoldRing ptr f z Ring{..} =
475 let !res = unsafeInlineIO $ withForeignPtr ringStart $ \p ->
476 go z p ptr
477 in res
478 where
479 go !acc !p !q
480 | p == q = return acc
481 | otherwise = do
482 x <- peek p
483 go (f acc x) (PTR_NEXT(p,a)) q
485-- XXX Can we remove MonadIO here?
486withForeignPtrM :: MonadIO m => ForeignPtr a -> (Ptr a -> m b) -> m b
487withForeignPtrM fp fn = do
488 r <- fn $ unsafeForeignPtrToPtr fp
489 liftIO $ touchForeignPtr fp
490 return r
492-- | Like unsafeFoldRing but with a monadic step function.
493{-# INLINE unsafeFoldRingM #-}
494unsafeFoldRingM :: forall m a b. (MonadIO m, Storable a)
495 => Ptr a -> (b -> a -> m b) -> b -> Ring a -> m b
496unsafeFoldRingM ptr f z Ring {..} =
497 withForeignPtrM ringStart $ \x -> go z x ptr
498 where
499 go !acc !start !end
500 | start == end = return acc
501 | otherwise = do
502 let !x = unsafeInlineIO $ peek start
503 acc1 <- f acc x
504 go acc1 (PTR_NEXT(start,a)) end
506-- | Fold the entire length of a ring buffer starting at the supplied ringHead
507-- pointer. Assuming the supplied ringHead pointer points to the oldest item,
508-- this would fold the ring starting from the oldest item to the newest item in
509-- the ring.
510--
511-- Note, this will crash on ring of 0 size.
512--
513{-# INLINE unsafeFoldRingFullM #-}
514unsafeFoldRingFullM :: forall m a b. (MonadIO m, Storable a)
515 => Ptr a -> (b -> a -> m b) -> b -> Ring a -> m b
516unsafeFoldRingFullM rh f z rb@Ring {..} =
517 withForeignPtrM ringStart $ \_ -> go z rh
518 where
519 go !acc !start = do
520 let !x = unsafeInlineIO $ peek start
521 acc' <- f acc x
522 let ptr = advance rb start
523 if ptr == rh
524 then return acc'
525 else go acc' ptr
527-- | Fold @Int@ items in the ring starting at @Ptr a@. Won't fold more
528-- than the length of the ring.
529--
530-- Note, this will crash on ring of 0 size.
531--
532{-# INLINE unsafeFoldRingNM #-}
533unsafeFoldRingNM :: forall m a b. (MonadIO m, Storable a)
534 => Int -> Ptr a -> (b -> a -> m b) -> b -> Ring a -> m b
535unsafeFoldRingNM count rh f z rb@Ring {..} =
536 withForeignPtrM ringStart $ \_ -> go count z rh
538 where
540 go 0 acc _ = return acc
541 go !n !acc !start = do
542 let !x = unsafeInlineIO $ peek start
543 acc' <- f acc x
544 let ptr = advance rb start
545 if ptr == rh || n == 0
546 then return acc'
547 else go (n - 1) acc' ptr
549data Tuple4' a b c d = Tuple4' !a !b !c !d deriving Show
551-- | Like slidingWindow but also provides the entire ring contents as an Array.
552-- The array reflects the state of the ring after inserting the incoming
553-- element.
554--
555-- IMPORTANT NOTE: The ring is mutable, therefore, the result of @(m (Array
556-- a))@ action depends on when it is executed. It does not capture the sanpshot
557-- of the ring at a particular time.
558{-# INLINE slidingWindowWith #-}
559slidingWindowWith :: forall m a b. (MonadIO m, Storable a, Unbox a)
560 => Int -> Fold m ((a, Maybe a), m (MutArray a)) b -> Fold m a b
561slidingWindowWith n (Fold step1 initial1 extract1) = Fold step initial extract
563 where
565 initial = do
566 if n <= 0
567 then error "Window size must be > 0"
568 else do
569 r <- initial1
570 (rb, rh) <- liftIO $ new n
571 return $
572 case r of
573 Partial s -> Partial $ Tuple4' rb rh (0 :: Int) s
574 Done b -> Done b
576 toArray foldRing rb rh = do
577 -- Using unpinned array here instead of pinned
578 arr <- liftIO $ MA.new n
579 let snoc' b a = liftIO $ MA.snocUnsafe b a
580 foldRing rh snoc' arr rb
582 step (Tuple4' rb rh i st) a
583 | i < n = do
584 rh1 <- liftIO $ unsafeInsert rb rh a
585 liftIO $ touchForeignPtr (ringStart rb)
586 let action = toArray unsafeFoldRingM rb (PTR_NEXT(rh, a))
587 r <- step1 st ((a, Nothing), action)
588 return $
589 case r of
590 Partial s -> Partial $ Tuple4' rb rh1 (i + 1) s
591 Done b -> Done b
592 | otherwise = do
593 old <- liftIO $ peek rh
594 rh1 <- liftIO $ unsafeInsert rb rh a
595 liftIO $ touchForeignPtr (ringStart rb)
596 r <- step1 st ((a, Just old), toArray unsafeFoldRingFullM rb rh1)
597 return $
598 case r of
599 Partial s -> Partial $ Tuple4' rb rh1 (i + 1) s
600 Done b -> Done b
602 extract (Tuple4' _ _ _ st) = extract1 st
604-- | @slidingWindow collector@ is an incremental sliding window
605-- fold that does not require all the intermediate elements in a computation.
606-- This maintains @n@ elements in the window, when a new element comes it slides
607-- out the oldest element and the new element along with the old element are
608-- supplied to the collector fold.
609--
610-- The 'Maybe' type is for the case when initially the window is filling and
611-- there is no old element.
612--
613{-# INLINE slidingWindow #-}
614slidingWindow :: forall m a b. (MonadIO m, Storable a, Unbox a)
615 => Int -> Fold m (a, Maybe a) b -> Fold m a b
616slidingWindow n f = slidingWindowWith n (lmap fst f)