2-- Module : Streamly.Internal.Data.Array.Stream.Foreign
3-- Copyright : (c) 2019 Composewell Technologies
4-- License : BSD3-3-Clause
5-- Maintainer : streamly@composewell.com
6-- Stability : experimental
9-- Combinators to efficiently manipulate streams of immutable arrays.
11module Streamly.Internal.Data.Array.Stream.Foreign
16 -- * Flattening to elements
29#if !defined(mingw32_HOST_OS)
42import Control.Monad.IO.Class (MonadIO(..))
43import Data.Word (Word8)
44import Foreign.ForeignPtr (withForeignPtr, touchForeignPtr)
45import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
46import Foreign.Ptr (minusPtr, plusPtr, castPtr)
47import Foreign.Storable (Storable(..))
48import GHC.ForeignPtr (ForeignPtr(..))
49import GHC.Ptr (Ptr(..))
50import Prelude hiding (length, null, last, map, (!!), read, concat, unlines)
52#if !defined(mingw32_HOST_OS)
53import Streamly.Internal.FileSystem.FDIO (IOVec(..))
55import Streamly.Internal.Data.Array.Foreign.Types (Array(..), length)
56import Streamly.Internal.Data.Fold.Types (Fold(..))
57import Streamly.Internal.Data.Stream.Serial (SerialT)
58import Streamly.Internal.Data.Stream.StreamK.Type (IsStream)
59import Streamly.Internal.Data.SVar (adaptState)
61import qualified Streamly.Internal.Data.Array.Foreign as A
62import qualified Streamly.Internal.Data.Array.Foreign.Types as A
63import qualified Streamly.Internal.Data.Array.Foreign.Mut.Types as MA
64import qualified Streamly.Internal.Data.Array.Stream.Mut.Foreign as AS
65import qualified Streamly.Internal.Data.Fold.Types as FL
66import qualified Streamly.Internal.Data.Stream.IsStream as S
67import qualified Streamly.Internal.Data.Stream.StreamD as D
69-------------------------------------------------------------------------------
71-------------------------------------------------------------------------------
73-- | @arraysOf n stream@ groups the elements in the input stream into arrays of
76-- > arraysOf n = Stream.chunksOf n (Array.writeN n)
79{-# INLINE arraysOf #-}
80arraysOf :: (IsStream t, MonadIO m, Storable a)
81 => Int -> t m a -> t m (Array a)
82arraysOf n str = D.fromStreamD $ A.arraysOf n (D.toStreamD str)
84-------------------------------------------------------------------------------
86-------------------------------------------------------------------------------
88-- XXX efficiently compare two streams of arrays. Two streams can have chunks
89-- of different sizes, we can handle that in the stream comparison abstraction.
90-- This could be useful e.g. to fast compare whether two files differ.
92-- | Convert a stream of arrays into a stream of their elements.
94-- Same as the following but more efficient:
96-- > concat = Stream.concatUnfold Array.read
100concat :: (IsStream t, MonadIO m, Storable a) => t m (Array a) -> t m a
101-- concat m = D.fromStreamD $ A.flattenArrays (D.toStreamD m)
102-- concat m = D.fromStreamD $ D.concatMap A.toStreamD (D.toStreamD m)
103concat m = D.fromStreamD $ D.concatUnfold A.read (D.toStreamD m)
105-- | Convert a stream of arrays into a stream of their elements reversing the
106-- contents of each array before flattening.
108-- > concatRev = Stream.concatUnfold Array.readRev
111{-# INLINE concatRev #-}
112concatRev :: (IsStream t, MonadIO m, Storable a) => t m (Array a) -> t m a
113concatRev m = D.fromStreamD $ A.flattenArraysRev (D.toStreamD m)
115-------------------------------------------------------------------------------
116-- Intersperse and append
117-------------------------------------------------------------------------------
119-- | Flatten a stream of arrays after inserting the given element between
123{-# INLINE interpose #-}
124interpose :: (MonadIO m, IsStream t, Storable a) => a -> t m (Array a) -> t m a
125interpose x = S.interpose x A.read
127{-# INLINE intercalateSuffix #-}
128intercalateSuffix :: (MonadIO m, IsStream t, Storable a)
129 => Array a -> t m (Array a) -> t m a
130intercalateSuffix arr = S.intercalateSuffix arr A.read
132-- | Flatten a stream of arrays appending the given element after each
136{-# INLINE interposeSuffix #-}
137interposeSuffix :: (MonadIO m, IsStream t, Storable a)
138 => a -> t m (Array a) -> t m a
139-- interposeSuffix x = D.fromStreamD . A.unlines x . D.toStreamD
140interposeSuffix x = S.interposeSuffix x A.read
142data FlattenState s a =
144 | InnerLoop s !(ForeignPtr a) !(Ptr a) !(Ptr a)
146{-# INLINE_NORMAL unlines #-}
147unlines :: forall m a. (MonadIO m, Storable a)
148 => a -> D.Stream m (Array a) -> D.Stream m a
149unlines sep (D.Stream step state) = D.Stream step' (OuterLoop state)
151 {-# INLINE_LATE step' #-}
152 step' gst (OuterLoop st) = do
153 r <- step (adaptState gst) st
155 D.Yield Array{..} s ->
156 let p = unsafeForeignPtrToPtr aStart
157 in D.Skip (InnerLoop s aStart p aEnd)
158 D.Skip s -> D.Skip (OuterLoop s)
161 step' _ (InnerLoop st _ p end) | p == end =
162 return $ D.Yield sep $ OuterLoop st
164 step' _ (InnerLoop st startf p end) = do
167 touchForeignPtr startf
169 return $ D.Yield x (InnerLoop st startf
170 (p `plusPtr` sizeOf (undefined :: a)) end)
172-------------------------------------------------------------------------------
174-------------------------------------------------------------------------------
176{-# INLINE_NORMAL packArraysChunksOf #-}
177packArraysChunksOf :: (MonadIO m, Storable a)
178 => Int -> D.Stream m (Array a) -> D.Stream m (Array a)
179packArraysChunksOf n str =
180 D.map A.unsafeFreeze $ AS.packArraysChunksOf n $ D.map A.unsafeThaw str
182-- XXX instead of writing two different versions of this operation, we should
183-- write it as a pipe.
184{-# INLINE_NORMAL lpackArraysChunksOf #-}
185lpackArraysChunksOf :: (MonadIO m, Storable a)
186 => Int -> Fold m (Array a) () -> Fold m (Array a) ()
187lpackArraysChunksOf n fld =
188 FL.map A.unsafeThaw $ AS.lpackArraysChunksOf n (FL.map A.unsafeFreeze fld)
190#if !defined(mingw32_HOST_OS)
192-- | @groupIOVecsOf maxBytes maxEntries@ groups arrays in the incoming stream
193-- to create a stream of 'IOVec' arrays with a maximum of @maxBytes@ bytes in
194-- each array and a maximum of @maxEntries@ entries in each array.
197{-# INLINE_NORMAL groupIOVecsOf #-}
198groupIOVecsOf :: MonadIO m
199 => Int -> Int -> D.Stream m (Array a) -> D.Stream m (Array IOVec)
200groupIOVecsOf n maxIOVLen str =
202 $ AS.groupIOVecsOf n maxIOVLen
203 $ D.map A.unsafeThaw str
206-- | Coalesce adjacent arrays in incoming stream to form bigger arrays of a
207-- maximum specified size in bytes.
210{-# INLINE compact #-}
211compact :: (MonadIO m, Storable a)
212 => Int -> SerialT m (Array a) -> SerialT m (Array a)
213compact n xs = D.fromStreamD $ packArraysChunksOf n (D.toStreamD xs)
215-------------------------------------------------------------------------------
217-------------------------------------------------------------------------------
223 | Yielding arr (SplitState s arr)
226-- | Split a stream of arrays on a given separator byte, dropping the separator
227-- and coalescing all the arrays between two separators into a single array.
230{-# INLINE_NORMAL _splitOn #-}
234 -> D.Stream m (Array Word8)
235 -> D.Stream m (Array Word8)
236_splitOn byte (D.Stream step state) = D.Stream step' (Initial state)
240 {-# INLINE_LATE step' #-}
241 step' gst (Initial st) = do
245 (arr1, marr2) <- A.breakOn byte arr
246 return $ case marr2 of
247 Nothing -> D.Skip (Buffering s arr1)
248 Just arr2 -> D.Skip (Yielding arr1 (Splitting s arr2))
249 D.Skip s -> return $ D.Skip (Initial s)
250 D.Stop -> return D.Stop
252 step' gst (Buffering st buf) = do
256 (arr1, marr2) <- A.breakOn byte arr
257 buf' <- A.spliceTwo buf arr1
258 return $ case marr2 of
259 Nothing -> D.Skip (Buffering s buf')
260 Just x -> D.Skip (Yielding buf' (Splitting s x))
261 D.Skip s -> return $ D.Skip (Buffering s buf)
263 if A.byteLength buf == 0
265 else D.Skip (Yielding buf Finishing)
267 step' _ (Splitting st buf) = do
268 (arr1, marr2) <- A.breakOn byte buf
269 return $ case marr2 of
270 Nothing -> D.Skip $ Buffering st arr1
271 Just arr2 -> D.Skip $ Yielding arr1 (Splitting st arr2)
273 step' _ (Yielding arr next) = return $ D.Yield arr next
274 step' _ Finishing = return D.Stop
276-- | Split a stream of arrays on a given separator byte, dropping the separator
277-- and coalescing all the arrays between two separators into a single array.
280{-# INLINE splitOn #-}
282 :: (IsStream t, MonadIO m)
287 D.fromStreamD $ D.splitInnerBy (A.breakOn byte) A.spliceTwo $ D.toStreamD s
289{-# INLINE splitOnSuffix #-}
291 :: (IsStream t, MonadIO m)
295-- splitOn byte s = D.fromStreamD $ A.splitOn byte $ D.toStreamD s
296splitOnSuffix byte s =
297 D.fromStreamD $ D.splitInnerBySuffix (A.breakOn byte) A.spliceTwo $ D.toStreamD s
299-------------------------------------------------------------------------------
301-------------------------------------------------------------------------------
303-- XXX Both of these implementations of splicing seem to perform equally well.
304-- We need to perform benchmarks over a range of sizes though.
306-- CAUTION! length must more than equal to lengths of all the arrays in the
308{-# INLINE spliceArraysLenUnsafe #-}
309spliceArraysLenUnsafe :: (MonadIO m, Storable a)
310 => Int -> SerialT m (MA.Array a) -> m (MA.Array a)
311spliceArraysLenUnsafe len buffered = do
312 arr <- liftIO $ MA.newArray len
313 end <- S.foldlM' writeArr (return $ MA.aEnd arr) buffered
314 return $ arr {MA.aEnd = end}
318 writeArr dst (MA.Array as ae _) =
319 liftIO $ withForeignPtr as $ \src -> do
320 let count = ae `minusPtr` src
321 A.memcpy (castPtr dst) (castPtr src) count
322 return $ dst `plusPtr` count
324{-# INLINE _spliceArrays #-}
325_spliceArrays :: (MonadIO m, Storable a)
326 => SerialT m (Array a) -> m (Array a)
328 buffered <- S.foldr S.cons S.nil s
329 len <- S.sum (S.map length buffered)
330 arr <- liftIO $ MA.newArray len
331 end <- S.foldlM' writeArr (return $ MA.aEnd arr) s
332 return $ A.unsafeFreeze $ arr {MA.aEnd = end}
336 writeArr dst (Array as ae) =
337 liftIO $ withForeignPtr as $ \src -> do
338 let count = ae `minusPtr` src
339 A.memcpy (castPtr dst) (castPtr src) count
340 return $ dst `plusPtr` count
342{-# INLINE _spliceArraysBuffered #-}
343_spliceArraysBuffered :: (MonadIO m, Storable a)
344 => SerialT m (Array a) -> m (Array a)
345_spliceArraysBuffered s = do
346 buffered <- S.foldr S.cons S.nil s
347 len <- S.sum (S.map length buffered)
348 A.unsafeFreeze <$> spliceArraysLenUnsafe len (S.map A.unsafeThaw s)
350{-# INLINE spliceArraysRealloced #-}
351spliceArraysRealloced :: forall m a. (MonadIO m, Storable a)
352 => SerialT m (Array a) -> m (Array a)
353spliceArraysRealloced s = do
354 let idst = liftIO $ MA.newArray (A.bytesToElemCount (undefined :: a)
357 arr <- S.foldlM' MA.spliceWithDoubling idst (S.map A.unsafeThaw s)
358 liftIO $ A.unsafeFreeze <$> MA.shrinkToFit arr
360-- | Given a stream of arrays, splice them all together to generate a single
361-- array. The stream must be /finite/.
364{-# INLINE toArray #-}
365toArray :: (MonadIO m, Storable a) => SerialT m (Array a) -> m (Array a)
366toArray = spliceArraysRealloced
367-- spliceArrays = _spliceArraysBuffered
369-- exponentially increasing sizes of the chunks upto the max limit.
370-- XXX this will be easier to implement with parsers/terminating folds
371-- With this we should be able to reduce the number of chunks/allocations.
372-- The reallocation/copy based toArray can also be implemented using this.
375{-# INLINE toArraysInRange #-}
376toArraysInRange :: (IsStream t, MonadIO m, Storable a)
377 => Int -> Int -> Fold m (Array a) b -> Fold m a b
378toArraysInRange low high (Fold step initial extract) =
382-- | Fold the input to a pure buffered stream (List) of arrays.
383{-# INLINE _toArraysOf #-}
384_toArraysOf :: (MonadIO m, Storable a)
385 => Int -> Fold m a (SerialT Identity (Array a))
386_toArraysOf n = FL.chunksOf n (A.writeNF n) FL.toStream
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list