on master help sign in
Refactor array modules (factor out array stream)

There is no functional change in this commit, only refactoring. * Update module documentation for arrays * Rearrange and re-group related functions together * Ability to use fold/unfold/foldMany/chunksOf in low level array modules so that we are able to express some of the functions more idiomatically. We have not done that in this change but with this change we should be able to do it later. * Separate out the array stream code from lower level modules to the higher level array stream module.

commitf448fb56439396953ee8e714c89b11cfd713c7d0 authorHarendra Kumar <harendra@composewell.com> dateFri 22 Jan 2021, 18:03 (6 years ago) parent8af89dd
23 files changed+1150−892
benchmark/Streamly/Benchmark/Data/Stream/StreamD.hs+1−1 benchmark/Streamly/Benchmark/FileSystem/Handle/Read.hs+6−5 benchmark/Streamly/Benchmark/FileSystem/Handle/ReadWrite.hs+7−6 benchmark/Streamly/Benchmark/Prelude/Serial/Split.hs+6−5 benchmark/Streamly/Benchmark/Unicode/Stream.hs+3−3 src/Streamly/FileSystem/FD.hs+1−1 src/Streamly/Internal/Data/Array/PrimInclude.hs+1−1 src/Streamly/Internal/Data/Array/Storable/Foreign.hs+3−26 src/Streamly/Internal/Data/Array/Storable/Foreign/Mut/Types.hs+423−586 src/Streamly/Internal/Data/Array/Storable/Foreign/Types.hs+36−111 src/Streamly/Internal/Data/Array/Stream/Mut/Foreign.hs+311 src/Streamly/Internal/Data/Stream/IsStream/Nesting.hs+9−8 src/Streamly/Internal/Data/Stream/StreamD/Generate.hs−22 src/Streamly/Internal/Data/Stream/StreamD/Nesting.hs+2−38 src/Streamly/Internal/Data/Stream/StreamD/Step.hs+39 src/Streamly/Internal/Data/Stream/StreamD/Transform.hs+13−13 src/Streamly/Internal/Data/Stream/StreamD/Type.hs+77−13 src/Streamly/Internal/Data/Unfold.hs+2−13 src/Streamly/Internal/Data/Unfold/Types.hs+15−1 src/Streamly/Internal/FileSystem/Handle.hs+2−2 src/Streamly/Internal/Memory/ArrayStream.hs+189−36 src/Streamly/Internal/Network/Socket.hs+2−1 streamly.cabal+2
benchmark/Streamly/Benchmark/Data/Stream/StreamD.hs+1−1
@@ -408,7 +408,7 @@ inspect $ 'concatMapRepl `hasNoType` ''SPEC
408408concatUnfoldRepl :: Int -> Int -> Int -> IO ()
409409concatUnfoldRepl outer inner n =
410410 S.drain
411− $ S.concatMapU
411+ $ S.concatUnfold
412412 (UF.lmap return (UF.replicateM inner))
413413 (sourceUnfoldrMN outer n)
414414
benchmark/Streamly/Benchmark/FileSystem/Handle/Read.hs+6−5
@@ -51,6 +51,7 @@ import Streamly.Benchmark.Common.Handle
5151#ifdef INSPECTION
5252import Streamly.Internal.Data.Stream.StreamD.Type (Step(..), GroupState)
5353
54+import qualified Streamly.Internal.Data.Array.Storable.Foreign.Mut.Types as MA
5455import qualified Streamly.Internal.Data.Stream.StreamD as D
5556import qualified Streamly.Internal.Data.Unfold as IUF
5657
@@ -162,7 +163,7 @@ readLast = S.last . S.unfold FH.read
162163inspect $ hasNoTypeClasses 'readLast
163164inspect $ 'readLast `hasNoType` ''Step -- S.unfold
164165inspect $ 'readLast `hasNoType` ''IUF.ConcatState -- FH.read/UF.concat
165−inspect $ 'readLast `hasNoType` ''A.ReadUState -- FH.read/A.read
166+inspect $ 'readLast `hasNoType` ''MA.ReadUState -- FH.read/A.read
166167#endif
167168
168169-- assert that flattenArrays constructors are not present
@@ -174,7 +175,7 @@ readCountBytes = S.length . S.unfold FH.read
174175inspect $ hasNoTypeClasses 'readCountBytes
175176inspect $ 'readCountBytes `hasNoType` ''Step -- S.unfold
176177inspect $ 'readCountBytes `hasNoType` ''IUF.ConcatState -- FH.read/UF.concat
177−inspect $ 'readCountBytes `hasNoType` ''A.ReadUState -- FH.read/A.read
178+inspect $ 'readCountBytes `hasNoType` ''MA.ReadUState -- FH.read/A.read
178179#endif
179180
180181-- | Count the number of lines in a file.
@@ -189,7 +190,7 @@ readCountLines =
189190inspect $ hasNoTypeClasses 'readCountLines
190191inspect $ 'readCountLines `hasNoType` ''Step
191192inspect $ 'readCountLines `hasNoType` ''IUF.ConcatState -- FH.read/UF.concat
192−inspect $ 'readCountLines `hasNoType` ''A.ReadUState -- FH.read/A.read
193+inspect $ 'readCountLines `hasNoType` ''MA.ReadUState -- FH.read/A.read
193194#endif
194195
195196-- | Count the number of words in a file.
@@ -213,7 +214,7 @@ readSumBytes = S.sum . S.unfold FH.read
213214inspect $ hasNoTypeClasses 'readSumBytes
214215inspect $ 'readSumBytes `hasNoType` ''Step
215216inspect $ 'readSumBytes `hasNoType` ''IUF.ConcatState -- FH.read/UF.concat
216−inspect $ 'readSumBytes `hasNoType` ''A.ReadUState -- FH.read/A.read
217+inspect $ 'readSumBytes `hasNoType` ''MA.ReadUState -- FH.read/A.read
217218#endif
218219
219220-- XXX When we mark this with INLINE and we have two benchmarks using S.drain
@@ -335,7 +336,7 @@ inspect $ 'chunksOf `hasNoType` ''Step
335336inspect $ 'chunksOf `hasNoType` ''GroupState
336337inspect $ 'chunksOf `hasNoType` ''AT.ArrayUnsafe -- AT.writeNUnsafe
337338inspect $ 'chunksOf `hasNoType` ''IUF.ConcatState -- FH.read/UF.concat
338−inspect $ 'chunksOf `hasNoType` ''A.ReadUState -- FH.read/A.read
339+inspect $ 'chunksOf `hasNoType` ''MA.ReadUState -- FH.read/A.read
339340#endif
340341
341342o_1_space_reduce_read_grouped :: BenchEnv -> [Benchmark]
benchmark/Streamly/Benchmark/FileSystem/Handle/ReadWrite.hs+7−6
@@ -42,8 +42,9 @@ import Streamly.Internal.Data.Stream.StreamD.Type (Step(..))
4242
4343import qualified Streamly.Internal.Data.Stream.StreamD.Type as D
4444import qualified Streamly.Internal.Data.Tuple.Strict as Strict
45−import qualified Streamly.Internal.Data.Array.Storable.Foreign as A
45+import qualified Streamly.Internal.Data.Array.Stream.Mut.Foreign as MAS
4646import qualified Streamly.Internal.Data.Array.Storable.Foreign.Types as AT
47+import qualified Streamly.Internal.Data.Array.Storable.Foreign.Mut.Types as MA
4748
4849import Test.Inspection
4950#endif
@@ -83,7 +84,7 @@ copyStream inh outh = S.fold (FH.write outh) (S.unfold FH.read inh)
8384inspect $ hasNoTypeClasses 'copyStream
8485inspect $ 'copyStream `hasNoType` ''Step -- S.unfold
8586inspect $ 'copyStream `hasNoType` ''IUF.ConcatState -- FH.read/UF.concat
86−inspect $ 'copyStream `hasNoType` ''A.ReadUState -- FH.read/A.read
87+inspect $ 'copyStream `hasNoType` ''MA.ReadUState -- FH.read/A.read
8788inspect $ 'copyStream `hasNoType` ''AT.ArrayUnsafe -- FH.write/writeNUnsafe
8889inspect $ 'copyStream `hasNoType` ''Strict.Tuple3' -- FH.write/chunksOf
8990#endif
@@ -109,7 +110,7 @@ readFromBytesNull inh devNull = IFH.fromBytes devNull $ S.unfold FH.read inh
109110#ifdef INSPECTION
110111inspect $ hasNoTypeClasses 'readFromBytesNull
111112inspect $ 'readFromBytesNull `hasNoType` ''Step
112−inspect $ 'readFromBytesNull `hasNoType` ''AT.SpliceState
113+inspect $ 'readFromBytesNull `hasNoType` ''MAS.SpliceState
113114inspect $ 'readFromBytesNull `hasNoType` ''AT.ArrayUnsafe -- FH.fromBytes/S.arraysOf
114115inspect $ 'readFromBytesNull `hasNoType` ''D.GroupState
115116#endif
@@ -123,7 +124,7 @@ readWithBufferOfFromBytesNull inh devNull =
123124#ifdef INSPECTION
124125inspect $ hasNoTypeClasses 'readWithBufferOfFromBytesNull
125126inspect $ 'readWithBufferOfFromBytesNull `hasNoType` ''Step
126−inspect $ 'readWithBufferOfFromBytesNull `hasNoType` ''AT.SpliceState
127+inspect $ 'readWithBufferOfFromBytesNull `hasNoType` ''MAS.SpliceState
127128inspect $ 'readWithBufferOfFromBytesNull `hasNoType` ''AT.ArrayUnsafe -- FH.fromBytes/S.arraysOf
128129inspect $ 'readWithBufferOfFromBytesNull `hasNoType` ''D.GroupState
129130#endif
@@ -173,7 +174,7 @@ writeReadWithBufferOf inh devNull = IUF.fold unf fld (defaultChunkSize, inh)
173174inspect $ hasNoTypeClasses 'writeReadWithBufferOf
174175inspect $ 'writeReadWithBufferOf `hasNoType` ''Step
175176inspect $ 'writeReadWithBufferOf `hasNoType` ''IUF.ConcatState -- FH.read/UF.concat
176−inspect $ 'writeReadWithBufferOf `hasNoType` ''A.ReadUState -- FH.read/A.read
177+inspect $ 'writeReadWithBufferOf `hasNoType` ''MA.ReadUState -- FH.read/A.read
177178inspect $ 'writeReadWithBufferOf `hasNoType` ''AT.ArrayUnsafe -- FH.write/writeNUnsafe
178179#endif
179180
@@ -191,7 +192,7 @@ writeRead inh devNull = IUF.fold unf fld inh
191192inspect $ hasNoTypeClasses 'writeRead
192193inspect $ 'writeRead `hasNoType` ''Step
193194inspect $ 'writeRead `hasNoType` ''IUF.ConcatState -- FH.read/UF.concat
194−inspect $ 'writeRead `hasNoType` ''A.ReadUState -- FH.read/A.read
195+inspect $ 'writeRead `hasNoType` ''MA.ReadUState -- FH.read/A.read
195196inspect $ 'writeRead `hasNoType` ''AT.ArrayUnsafe -- FH.write/writeNUnsafe
196197#endif
197198
benchmark/Streamly/Benchmark/Prelude/Serial/Split.hs+6−5
@@ -1,4 +1,4 @@
1− -- |
1+-- |
22-- Module : Streamly.Benchmark.Prelude.Serial.Split
33-- Copyright : (c) 2019 Composewell Technologies
44-- License : BSD-3-Clause
@@ -41,6 +41,7 @@ import Streamly.Benchmark.Common.Handle
4141#ifdef INSPECTION
4242import Streamly.Internal.Data.Stream.StreamD.Type (Step(..))
4343
44+import qualified Streamly.Internal.Data.Array.Storable.Foreign.Mut.Types as MA
4445import qualified Streamly.Internal.Data.Unfold as IUF
4546
4647import Test.Inspection
@@ -66,7 +67,7 @@ splitOn inh =
6667inspect $ hasNoTypeClasses 'splitOn
6768inspect $ 'splitOn `hasNoType` ''Step
6869inspect $ 'splitOn `hasNoType` ''IUF.ConcatState -- FH.read/UF.concat
69−inspect $ 'splitOn `hasNoType` ''A.ReadUState -- FH.read/A.read
70+inspect $ 'splitOn `hasNoType` ''MA.ReadUState -- FH.read/A.read
7071#endif
7172
7273-- | Split suffix on line feed.
@@ -79,7 +80,7 @@ splitOnSuffix inh =
7980inspect $ hasNoTypeClasses 'splitOnSuffix
8081inspect $ 'splitOnSuffix `hasNoType` ''Step
8182inspect $ 'splitOnSuffix `hasNoType` ''IUF.ConcatState -- FH.read/UF.concat
82−inspect $ 'splitOnSuffix `hasNoType` ''A.ReadUState -- FH.read/A.read
83+inspect $ 'splitOnSuffix `hasNoType` ''MA.ReadUState -- FH.read/A.read
8384#endif
8485
8586-- | Split suffix with line feed.
@@ -92,7 +93,7 @@ splitWithSuffix inh =
9293inspect $ hasNoTypeClasses 'splitWithSuffix
9394inspect $ 'splitWithSuffix `hasNoType` ''Step
9495inspect $ 'splitWithSuffix `hasNoType` ''IUF.ConcatState -- FH.read/UF.concat
95−inspect $ 'splitWithSuffix `hasNoType` ''A.ReadUState -- FH.read/A.read
96+inspect $ 'splitWithSuffix `hasNoType` ''MA.ReadUState -- FH.read/A.read
9697#endif
9798
9899-- | Split on line feed.
@@ -123,7 +124,7 @@ wordsBy inh =
123124inspect $ hasNoTypeClasses 'wordsBy
124125inspect $ 'wordsBy `hasNoType` ''Step
125126inspect $ 'wordsBy `hasNoType` ''IUF.ConcatState -- FH.read/UF.concat
126−inspect $ 'wordsBy `hasNoType` ''A.ReadUState -- FH.read/A.read
127+inspect $ 'wordsBy `hasNoType` ''MA.ReadUState -- FH.read/A.read
127128#endif
128129
129130-- | Split on a word8 sequence.
benchmark/Streamly/Benchmark/Unicode/Stream.hs+3−3
@@ -40,8 +40,8 @@ import Streamly.Benchmark.Common.Handle
4040import Foreign.Storable (Storable)
4141import Streamly.Internal.Data.Stream.StreamD.Type (Step(..))
4242import qualified Streamly.Internal.Data.Tuple.Strict as Strict
43−import qualified Streamly.Internal.Data.Array.Storable.Foreign as A
4443import qualified Streamly.Internal.Data.Array.Storable.Foreign.Types as AT
44+import qualified Streamly.Internal.Data.Array.Storable.Foreign.Mut.Types as MA
4545
4646import Test.Inspection
4747#endif
@@ -201,7 +201,7 @@ copyStreamLatin1' inh outh =
201201inspect $ hasNoTypeClasses 'copyStreamLatin1'
202202inspect $ 'copyStreamLatin1' `hasNoType` ''Step
203203inspect $ 'copyStreamLatin1' `hasNoType` ''IUF.ConcatState -- FH.read/UF.concat
204−inspect $ 'copyStreamLatin1' `hasNoType` ''A.ReadUState -- FH.read/A.read
204+inspect $ 'copyStreamLatin1' `hasNoType` ''MA.ReadUState -- FH.read/A.read
205205inspect $ 'copyStreamLatin1' `hasNoType` ''AT.ArrayUnsafe -- FH.write/writeNUnsafe
206206inspect $ 'copyStreamLatin1' `hasNoType` ''Strict.Tuple3' -- FH.write/chunksOf
207207#endif
@@ -218,7 +218,7 @@ copyStreamLatin1 inh outh =
218218inspect $ hasNoTypeClasses 'copyStreamLatin1
219219inspect $ 'copyStreamLatin1 `hasNoType` ''Step
220220inspect $ 'copyStreamLatin1 `hasNoType` ''IUF.ConcatState -- FH.read/UF.concat
221−inspect $ 'copyStreamLatin1 `hasNoType` ''A.ReadUState -- FH.read/A.read
221+inspect $ 'copyStreamLatin1 `hasNoType` ''MA.ReadUState -- FH.read/A.read
222222inspect $ 'copyStreamLatin1 `hasNoType` ''AT.ArrayUnsafe -- FH.write/writeNUnsafe
223223inspect $ 'copyStreamLatin1 `hasNoType` ''Strict.Tuple3' -- FH.write/chunksOf
224224#endif
src/Streamly/FileSystem/FD.hs+1−1
@@ -137,7 +137,7 @@ import Streamly.Internal.Data.Stream.Serial (SerialT)
137137import Streamly.Internal.Data.Stream.StreamK.Type (IsStream, mkStream)
138138
139139#if !defined(mingw32_HOST_OS)
140−import Streamly.Internal.Data.Array.Storable.Foreign.Types (groupIOVecsOf)
140+import Streamly.Internal.Memory.ArrayStream (groupIOVecsOf)
141141import Streamly.Internal.Data.Stream.StreamD (toStreamD)
142142import Streamly.Internal.Data.Stream.StreamD.Type (fromStreamD)
143143import qualified Streamly.FileSystem.FDIO as RawIO hiding (write)
src/Streamly/Internal/Data/Array/PrimInclude.hs+1−1
@@ -173,7 +173,7 @@ last arr = readIndex arr (length arr - 1)
173173concat :: (IsStream t, MonadIO m, Prim a) => t m (Array a) -> t m a
174174-- concat m = D.fromStreamD $ A.flattenArrays (D.toStreamD m)
175175-- concat m = D.fromStreamD $ D.concatMap A.toStreamD (D.toStreamD m)
176−concat m = D.fromStreamD $ D.concatMapU read (D.toStreamD m)
176+concat m = D.fromStreamD $ D.concatUnfold read (D.toStreamD m)
177177
178178-- | Coalesce adjacent arrays in incoming stream to form bigger arrays of a
179179-- maximum specified size in bytes.
src/Streamly/Internal/Data/Array/Storable/Foreign.hs+3−26
@@ -68,7 +68,6 @@ module Streamly.Internal.Data.Array.Storable.Foreign
6868 , A.toStream
6969 , A.toStreamRev
7070 , read
71− , ReadUState (..)
7271 , unsafeRead
7372 -- , readChunksOf
7473
@@ -147,7 +146,7 @@ import Data.Semigroup ((<>))
147146import Data.Word (Word8)
148147-- import Data.Functor.Identity (Identity)
149148import Foreign.C.String (CString)
150−import Foreign.ForeignPtr (withForeignPtr, touchForeignPtr, castForeignPtr)
149+import Foreign.ForeignPtr (withForeignPtr, castForeignPtr)
151150import Foreign.Ptr (plusPtr, castPtr)
152151import Foreign.Storable (Storable(..))
153152import Prelude hiding (length, null, last, map, (!!), read, concat)
@@ -169,6 +168,7 @@ import qualified Streamly.Internal.Data.Fold as FL
169168import qualified Streamly.Internal.Data.Stream.Prelude as P
170169import qualified Streamly.Internal.Data.Stream.Serial as Serial
171170import qualified Streamly.Internal.Data.Stream.StreamD as D
171+import qualified Streamly.Internal.Data.Unfold as Unfold
172172import qualified Streamly.Memory.Ring as RB
173173
174174-------------------------------------------------------------------------------
@@ -204,35 +204,12 @@ fromStream = P.foldOnce A.write
204204-- Elimination
205205-------------------------------------------------------------------------------
206206
207−data ReadUState a = ReadUState
208− {-# UNPACK #-} !(ForeignPtr a) -- foreign ptr with end of array pointer
209− {-# UNPACK #-} !(Ptr a) -- current pointer
210−
211207-- | Unfold an array into a stream.
212208--
213209-- @since 0.7.0
214210{-# INLINE_NORMAL read #-}
215211read :: forall m a. (Monad m, Storable a) => Unfold m (Array a) a
216−read = Unfold step inject
217− where
218−
219− inject (Array (ForeignPtr start contents) (Ptr end)) =
220− return $ ReadUState (ForeignPtr end contents) (Ptr start)
221−
222− {-# INLINE_LATE step #-}
223− step (ReadUState fp@(ForeignPtr end _) p) | p == Ptr end =
224− let x = A.unsafeInlineIO $ touchForeignPtr fp
225− in x `seq` return D.Stop
226− step (ReadUState fp p) = do
227− -- unsafeInlineIO allows us to run this in Identity monad for pure
228− -- toList/foldr case which makes them much faster due to not
229− -- accumulating the list and fusing better with the pure consumers.
230− --
231− -- This should be safe as the array contents are guaranteed to be
232− -- evaluated/written to before we peek at them.
233− let !x = A.unsafeInlineIO $ peek p
234− return $ D.Yield x
235− (ReadUState fp (p `plusPtr` sizeOf (undefined :: a)))
212+read = Unfold.lmap A.unsafeThaw MA.read
236213
237214-- | Unfold an array into a stream, does not check the end of the array, the
238215-- user is responsible for terminating the stream within the array bounds. For
src/Streamly/Internal/Data/Array/Storable/Foreign/Mut/Types.hs+423−586
@@ -1,71 +1,69 @@
11{-# LANGUAGE UnboxedTuples #-}
22
3−#include "inline.hs"
4−
53-- |
64-- Module : Streamly.Internal.Data.Array.Storable.Foreign.Mut.Types
75-- Copyright : (c) 2020 Composewell Technologies
8−--
96-- License : BSD3-3-Clause
107-- Maintainer : streamly@composewell.com
118-- Stability : experimental
129-- Portability : GHC
1310--
11+-- Unboxed pinned mutable array type for 'Storable' types with an option to use
12+-- foreign (non-GHC) memory allocators. Fulfils the following goals:
13+--
14+-- * Random access (array)
15+-- * Efficient storage (unboxed)
16+-- * Performance (unboxed access)
17+-- * Performance - in-place operations (mutable)
18+-- * Performance - GC (pinned, mutable)
19+-- * interfacing with OS (pinned)
20+-- * Fragmentation control (foreign allocators)
21+--
22+-- Stream and Fold APIs allow easy, efficient and convenient operations on
23+-- arrays.
24+
1425module Streamly.Internal.Data.Array.Storable.Foreign.Mut.Types
1526 (
27+ -- * Type
28+ -- $arrayNotes
1629 Array (..)
17− , mutableArray
1830
1931 -- * Construction
32+ , mutableArray
2033 , withNewArray
2134 , newArray
2235 , newArrayAligned
2336 , newArrayAlignedUnmanaged
2437 , newArrayAlignedAllocWith
25− , unsafeSnoc
26− , snoc
27− , spliceWith
28− , spliceWithDoubling
29− , spliceTwo
3038
39+ -- * From containers
3140 , fromList
3241 , fromListN
3342 , fromStreamDN
34− -- , fromStreamD
43+ , fromStreamD
3544
36− -- * Streams of arrays
37− , fromStreamDArraysOf
38− , FlattenState (..) -- for inspection testing
39− , flattenArrays
40− , flattenArraysRev
41− , packArraysChunksOf
42− , SpliceState (..)
43− , lpackArraysChunksOf
44−#if !defined(mingw32_HOST_OS)
45− , groupIOVecsOf
46−#endif
47− , splitOn
48− , breakOn
45+ -- * Resizing
46+ , realloc
47+ , shrinkToFit
4948
50− -- * Elimination
51− , unsafeIndexIO
52− , unsafeIndex
49+ -- * Size
5350 , length
5451 , byteLength
5552 , byteCapacity
56− , foldl'
57− , foldr
58− , splitAt
5953
60− , toStreamD
61− , toStreamDRev
62− , toStreamK
63− , toStreamKRev
64− , toList
54+ -- * Random access
55+ , unsafeIndexIO
56+ , unsafeIndex
57+
58+ -- * Mutation
59+ , unsafeSnoc
60+ , snoc
6561
66− , unlines
62+ -- * Folding
63+ , foldl'
64+ , foldr
6765
68− -- * Folds
66+ -- * Composable Folds
6967 , toArrayMinChunk
7068 , writeNAllocWith
7169 , writeN
@@ -76,24 +74,50 @@ module Streamly.Internal.Data.Array.Storable.Foreign.Mut.Types
7674 , write
7775 , writeAligned
7876
77+ -- * Unfolds
78+ , ReadUState
79+ , read
80+ , flattenArrays
81+ , flattenArraysRev
82+
83+ -- * To containers
84+ , toStreamD
85+ , toStreamDRev
86+ , toStreamK
87+ , toStreamKRev
88+ , toList
89+
90+ -- * Combining
91+ , spliceWith
92+ , spliceWithDoubling
93+ , spliceTwo
94+
95+ -- * Splitting
96+ , breakOn
97+ , splitAt
98+
99+ -- * Stream of arrays
100+ , arraysOf
101+ , bufferChunks
102+ , writeChunks
103+
79104 -- * Utilities
80105 , defaultChunkSize
81106 , mkChunkSize
82107 , mkChunkSizeKB
108+ , bytesToElemCount
83109 , unsafeInlineIO
84− , realloc
85− , shrinkToFit
86110 , memcpy
87111 , memcmp
88− , bytesToElemCount
89112 )
90113where
91114
115+#include "inline.hs"
116+
92117import Control.Exception (assert)
93118import Control.DeepSeq (NFData(..))
94119import Control.Monad (when, void)
95120import Control.Monad.IO.Class (MonadIO(..))
96−import Data.Bifunctor (first)
97121import Data.Functor.Identity (runIdentity)
98122#if __GLASGOW_HASKELL__ < 808
99123import Data.Semigroup (Semigroup(..))
@@ -109,11 +133,8 @@ import GHC.Exts (IsList, IsString(..))
109133import GHC.ForeignPtr (ForeignPtr(..))
110134import GHC.IO (IO(IO), unsafePerformIO)
111135import GHC.Ptr (Ptr(..))
112−#if !defined(mingw32_HOST_OS)
113−import Streamly.FileSystem.FDIO (IOVec(..))
114−#endif
115136import Streamly.Internal.Data.Fold.Types (Fold(..))
116−import Streamly.Internal.Data.Tuple.Strict (Tuple'(..))
137+import Streamly.Internal.Data.Unfold.Types (Unfold(..))
117138import Streamly.Internal.Data.SVar (adaptState)
118139import Text.Read (readPrec, readListPrec, readListPrecDefault)
119140
@@ -137,61 +158,27 @@ plusForeignPtr :: ForeignPtr a -> Int -> ForeignPtr b
137158plusForeignPtr (ForeignPtr addr c) (I# d) = ForeignPtr (plusAddr# addr d) c
138159#endif
139160
140−-------------------------------------------------------------------------------
141−-- Design Notes
142−-------------------------------------------------------------------------------
143−
144−-- There are two goals that we want to fulfill with arrays. One, holding large
145−-- amounts of data in non-GC memory and the ability to pass this data to and
146−-- from the operating system without an extra copy overhead. Two, allow random
147−-- access to elements based on their indices. The first one falls in the
148−-- category of storage buffers while the second one falls in the category of
149−-- maps/multisets/hashmaps.
150−--
151−-- For the first requirement we use an array of Storables and store it in a
152−-- ForeignPtr. We can have both immutable and mutable variants of this array
153−-- using wrappers over the same underlying type.
154−--
155−-- For the second requirement, we need a separate type for arrays of
156−-- polymorphic values, for example vectors of handler functions, lookup tables.
157−-- We can call this type a "vector" in contrast to arrays. It should not
158−-- require Storable instance for the type. In that case we need to use an
159−-- Array# instead of a ForeignPtr. This type of array would not reduce the GC
160−-- overhead much because each element of the array still needs to be scanned by
161−-- the GC. However, it can store polymorphic elements and allow random access
162−-- to those. But in most cases random access means storage, and it means we
163−-- need to avoid GC scanning except in cases of trivially small storage. One
164−-- way to achieve that would be to put the array in a Compact region. However,
165−-- if and when we mutate this, we will have to use a manual GC copying out to
166−-- another CR and freeing the old one.
167−
168161-------------------------------------------------------------------------------
169162-- Array Data Type
170163-------------------------------------------------------------------------------
171164
172−-- We require that an array stores only Storable. Arrays are used for buffering
173−-- data while streams are used for processing. If you want something to be
174−-- buffered it better be Storable so that we can store it in non-GC memory.
165+-- $arrayNotes
175166--
176−-- We can use a "Storable" constraint in the Array type and the Constraint can
167+-- We can use a 'Storable' constraint in the Array type and the constraint can
177168-- be automatically provided to a function that pattern matches on the Array
178169-- type. However, it has huge performance cost, so we do not use it.
179−-- XXX Can this cost be alleviated in GHC-8.10 specialization fix?
170+-- Investigate a GHC improvement possiblity.
180171--
181−-- XXX Another way to not require the Storable constraint in array operations
182−-- is to store the elemSize in the array at construction and use that instead
183−-- of using sizeOf. Need to charaterize perf cost of this.
184−--
185−-- XXX rename the fields to "start, next, end".
172+-- XXX Rename the fields to better names.
186173--
187174data Array a =
188175#ifdef DEVBUILD
189176 Storable a =>
190177#endif
191178 Array
192− { aStart :: {-# UNPACK #-} !(ForeignPtr a) -- first address
193− , aEnd :: {-# UNPACK #-} !(Ptr a) -- first unused address
194− , aBound :: {-# UNPACK #-} !(Ptr a) -- first address beyond allocated memory
179+ { aStart :: {-# UNPACK #-} !(ForeignPtr a) -- ^ first address
180+ , aEnd :: {-# UNPACK #-} !(Ptr a) -- ^ first unused address
181+ , aBound :: {-# UNPACK #-} !(Ptr a) -- ^ first address beyond allocated memory
195182 }
196183
197184{-# INLINE mutableArray #-}
@@ -318,6 +305,10 @@ withNewArray count f = do
318305 arr <- newArray count
319306 withForeignPtr (aStart arr) $ \p -> f p >> return arr
320307
308+-------------------------------------------------------------------------------
309+-- snoc
310+-------------------------------------------------------------------------------
311+
321312-- XXX grow the array when we are beyond bound.
322313--
323314-- Internal routine for when the array is being created. Appends one item at
@@ -358,6 +349,10 @@ snoc arr@Array {..} x =
358349 touchForeignPtr aStart
359350 return $ arr {aEnd = aEnd `plusPtr` sizeOf (undefined :: a)}
360351
352+-------------------------------------------------------------------------------
353+-- re-allocate
354+-------------------------------------------------------------------------------
355+
361356-- | Reallocate the array to the specified size in bytes. If the size is less
362357-- than the original array the array gets truncated.
363358{-# NOINLINE reallocAligned #-}
@@ -381,117 +376,59 @@ reallocAligned alignSize newSize Array{..} = do
381376realloc :: forall a. Storable a => Int -> Array a -> IO (Array a)
382377realloc = reallocAligned (alignment (undefined :: a))
383378
384−-- | Create an 'Array' from the first N elements of a list. The array is
385−-- allocated to size N, if the list terminates before N elements then the
386−-- array may hold less than N elements.
387−--
388−-- @since 0.7.0
389−{-# INLINABLE fromListN #-}
390−fromListN :: Storable a => Int -> [a] -> Array a
391−fromListN n xs = unsafePerformIO $ fromStreamDN n $ D.fromList xs
379+-------------------------------------------------------------------------------
380+-- Elimination
381+-------------------------------------------------------------------------------
392382
393−-- | Create an 'Array' from a list. The list must be of finite size.
383+-- | Return element at the specified index without checking the bounds.
394384--
395−-- @since 0.7.0
396−{-# INLINABLE fromList #-}
397−fromList :: Storable a => [a] -> Array a
398−fromList xs = unsafePerformIO $ fromStreamD $ D.fromList xs
399−
400−{-# INLINE_NORMAL fromStreamDN #-}
401−fromStreamDN :: forall m a. (MonadIO m, Storable a)
402− => Int -> D.Stream m a -> m (Array a)
403−fromStreamDN limit str = do
404− arr <- liftIO $ newArray limit
405− end <- D.foldlM' fwrite (return $ aEnd arr) $ D.take limit str
406− return $ arr {aEnd = end}
407−
408− where
385+-- Unsafe because it does not check the bounds of the array.
386+{-# INLINE_NORMAL unsafeIndexIO #-}
387+unsafeIndexIO :: forall a. Storable a => Array a -> Int -> IO a
388+unsafeIndexIO Array {..} i =
389+ withForeignPtr aStart $ \p -> do
390+ let elemSize = sizeOf (undefined :: a)
391+ elemOff = p `plusPtr` (elemSize * i)
392+ assert (i >= 0 && elemOff `plusPtr` elemSize <= aEnd)
393+ (return ())
394+ peek elemOff
409395
410− fwrite ptr x = do
411− liftIO $ poke ptr x
412− return $ ptr `plusPtr` sizeOf (undefined :: a)
396+-- | Return element at the specified index without checking the bounds.
397+{-# INLINE_NORMAL unsafeIndex #-}
398+unsafeIndex :: forall a. Storable a => Array a -> Int -> a
399+unsafeIndex arr i = let !r = unsafeInlineIO $ unsafeIndexIO arr i in r
413400
414−-- CAUTION: a very large number (millions) of arrays can degrade performance
415−-- due to GC overhead because we need to buffer the arrays before we flatten
416−-- all the arrays.
417−--
418−-- We could take the approach of doubling the memory allocation on each
419−-- overflow. This would result in more or less the same amount of copying as in
420−-- the chunking approach. However, if we have to shrink in the end then it may
421−-- result in an extra copy of the entire data.
401+-- | /O(1)/ Get the byte length of the array.
422402--
423−{-# INLINE fromStreamD #-}
424−fromStreamD :: (MonadIO m, Storable a) => D.Stream m a -> m (Array a)
425−fromStreamD m = do
426− let s = fromStreamDArraysOf defaultChunkSize m
427− buffered <- D.foldr K.cons K.nil s
428− len <- K.foldl' (+) 0 (K.map length buffered)
429− fromStreamDN len $ flattenArrays $ D.fromStreamK buffered
430−{-
431−fromStreamD m = foldOnce write m
432− where
433− foldOnce (Fold step begin done) = D.foldlMx' step begin done
434−-}
435−
436−
437−-- Splice two immutable arrays creating a new array.
438−{-# INLINE spliceTwo #-}
439−spliceTwo :: (MonadIO m, Storable a) => Array a -> Array a -> m (Array a)
440−spliceTwo arr1 arr2 = do
441− let src1 = unsafeForeignPtrToPtr (aStart arr1)
442− src2 = unsafeForeignPtrToPtr (aStart arr2)
443− len1 = aEnd arr1 `minusPtr` src1
444− len2 = aEnd arr2 `minusPtr` src2
445−
446− arr <- liftIO $ newArray (len1 + len2)
447− let dst = unsafeForeignPtrToPtr (aStart arr)
448−
449− -- XXX Should we use copyMutableByteArray# instead? Is there an overhead to
450− -- ccall?
451− liftIO $ do
452− memcpy (castPtr dst) (castPtr src1) len1
453− touchForeignPtr (aStart arr1)
454− memcpy (castPtr (dst `plusPtr` len1)) (castPtr src2) len2
455− touchForeignPtr (aStart arr2)
456− return arr { aEnd = dst `plusPtr` (len1 + len2) }
457−
458−-- Splice an array into a pre-reserved mutable array. The user must ensure
459−-- that there is enough space in the mutable array.
460−{-# INLINE spliceWith #-}
461−spliceWith :: (MonadIO m) => Array a -> Array a -> m (Array a)
462−spliceWith dst@(Array _ end bound) src =
463− liftIO $ do
464− let srcLen = byteLength src
465− if end `plusPtr` srcLen > bound
466− then error
467− "Bug: spliceIntoUnsafe: Not enough space in the target array"
468− else withForeignPtr (aStart dst) $ \_ ->
469− withForeignPtr (aStart src) $ \psrc -> do
470− let pdst = aEnd dst
471− memcpy (castPtr pdst) (castPtr psrc) srcLen
472− return $ dst {aEnd = pdst `plusPtr` srcLen}
403+-- @since 0.7.0
404+{-# INLINE byteLength #-}
405+byteLength :: Array a -> Int
406+byteLength Array{..} =
407+ let p = unsafeForeignPtrToPtr aStart
408+ len = aEnd `minusPtr` p
409+ in assert (len >= 0) len
473410
474−-- Splice a new array into a preallocated mutable array, doubling the space if
475−-- there is no space in the target array.
476−{-# INLINE spliceWithDoubling #-}
477−spliceWithDoubling :: (MonadIO m, Storable a)
478− => Array a -> Array a -> m (Array a)
479−spliceWithDoubling dst@(Array start end bound) src = do
480− assert (end <= bound) (return ())
481− let srcLen = aEnd src `minusPtr` unsafeForeignPtrToPtr (aStart src)
411+-- | /O(1)/ Get the length of the array i.e. the number of elements in the
412+-- array.
413+--
414+-- @since 0.7.0
415+{-# INLINE length #-}
416+length :: forall a. Storable a => Array a -> Int
417+length arr = byteLength arr `div` sizeOf (undefined :: a)
482418
483− dst1 <-
484− if end `plusPtr` srcLen >= bound
485− then do
486− let oldStart = unsafeForeignPtrToPtr start
487− oldSize = end `minusPtr` oldStart
488− newSize = max (oldSize * 2) (oldSize + srcLen)
489− liftIO $ realloc newSize dst
490− else return dst
491− spliceWith dst1 src
419+-- | Get the total capacity of an array. An array may have space reserved
420+-- beyond the current used length of the array.
421+--
422+-- /Internal/
423+{-# INLINE byteCapacity #-}
424+byteCapacity :: Array a -> Int
425+byteCapacity Array{..} =
426+ let p = unsafeForeignPtrToPtr aStart
427+ len = aBound `minusPtr` p
428+ in assert (len >= 0) len
492429
493430-------------------------------------------------------------------------------
494−-- Streams of arrays
431+-- Streams of arrays - Creation
495432-------------------------------------------------------------------------------
496433
497434data GroupState s start end bound
@@ -500,13 +437,20 @@ data GroupState s start end bound
500437 | GroupYield start end bound (GroupState s start end bound)
501438 | GroupFinish
502439
503−-- | @fromStreamArraysOf n stream@ groups the input stream into a stream of
440+-- | @arraysOf n stream@ groups the input stream into a stream of
504441-- arrays of size n.
505−{-# INLINE_NORMAL fromStreamDArraysOf #-}
506−fromStreamDArraysOf :: forall m a. (MonadIO m, Storable a)
442+--
443+-- @arraysOf n = StreamD.foldMany (Array.writeN n)@
444+--
445+-- /Internal/
446+{-# INLINE_NORMAL arraysOf #-}
447+arraysOf :: forall m a. (MonadIO m, Storable a)
507448 => Int -> D.Stream m a -> D.Stream m (Array a)
508−-- fromStreamDArraysOf n str = D.groupsOf n (writeN n) str
509−fromStreamDArraysOf n (D.Stream step state) =
449+-- XXX the idiomatic implementation leads to large regression in the D.reverse'
450+-- benchmark. It seems it has difficulty producing optimized code when
451+-- converting to StreamK. Investigate GHC optimizations.
452+-- arraysOf n = D.foldMany (writeN n)
453+arraysOf n (D.Stream step state) =
510454 D.Stream step' (GroupStart state)
511455
512456 where
@@ -538,12 +482,57 @@ fromStreamDArraysOf n (D.Stream step state) =
538482
539483 step' _ GroupFinish = return D.Stop
540484
541−-- XXX concatMap does not seem to have the best possible performance so we have
542−-- a custom way to concat arrays.
485+-- XXX buffer to a list instead?
486+-- | Buffer the stream into arrays in memory.
487+{-# INLINE bufferChunks #-}
488+bufferChunks :: (MonadIO m, Storable a) =>
489+ D.Stream m a -> m (K.Stream m (Array a))
490+bufferChunks m = D.foldr K.cons K.nil $ arraysOf defaultChunkSize m
491+
492+-------------------------------------------------------------------------------
493+-- Streams of arrays - flatten
494+-------------------------------------------------------------------------------
495+
496+data ReadUState a = ReadUState
497+ {-# UNPACK #-} !(ForeignPtr a) -- foreign ptr with end of array pointer
498+ {-# UNPACK #-} !(Ptr a) -- current pointer
499+
500+-- | Unfold an array into a stream.
501+--
502+-- @since 0.7.0
503+{-# INLINE_NORMAL read #-}
504+read :: forall m a. (Monad m, Storable a) => Unfold m (Array a) a
505+read = Unfold step inject
506+ where
507+
508+ inject (Array (ForeignPtr start contents) (Ptr end) _) =
509+ return $ ReadUState (ForeignPtr end contents) (Ptr start)
510+
511+ {-# INLINE_LATE step #-}
512+ step (ReadUState fp@(ForeignPtr end _) p) | p == Ptr end =
513+ let x = unsafeInlineIO $ touchForeignPtr fp
514+ in x `seq` return D.Stop
515+ step (ReadUState fp p) = do
516+ -- unsafeInlineIO allows us to run this in Identity monad for pure
517+ -- toList/foldr case which makes them much faster due to not
518+ -- accumulating the list and fusing better with the pure consumers.
519+ --
520+ -- This should be safe as the array contents are guaranteed to be
521+ -- evaluated/written to before we peek at them.
522+ let !x = unsafeInlineIO $ peek p
523+ return $ D.Yield x
524+ (ReadUState fp (p `plusPtr` sizeOf (undefined :: a)))
525+
543526data FlattenState s a =
544527 OuterLoop s
545528 | InnerLoop s !(ForeignPtr a) !(Ptr a) !(Ptr a)
546529
530+-- | Use the "read" unfold instead.
531+--
532+-- @flattenArrays = concatUnfold read@
533+--
534+-- We can try this if there are any fusion issues in the unfold.
535+--
547536{-# INLINE_NORMAL flattenArrays #-}
548537flattenArrays :: forall m a. (MonadIO m, Storable a)
549538 => D.Stream m (Array a) -> D.Stream m a
@@ -601,350 +590,43 @@ flattenArraysRev (D.Stream step state) = D.Stream step' (OuterLoop state)
601590 return $ D.Yield x (InnerLoop st startf
602591 (p `plusPtr` negate (sizeOf (undefined :: a))) end)
603592
604−data SpliceState s arr
605− = SpliceInitial s
606− | SpliceBuffering s arr
607− | SpliceYielding arr (SpliceState s arr)
608− | SpliceFinish
609−
610−-- XXX can use general grouping combinators to achieve this?
611−-- | Coalesce adjacent arrays in incoming stream to form bigger arrays of a
612−-- maximum specified size. Note that if a single array is bigger than the
613−-- specified size we do not split it to fit. When we coalesce multiple arrays
614−-- if the size would exceed the specified size we do not coalesce therefore the
615−-- actual array size may be less than the specified chunk size.
616−--
617−-- @since 0.7.0
618−{-# INLINE_NORMAL packArraysChunksOf #-}
619−packArraysChunksOf :: (MonadIO m, Storable a)
620− => Int -> D.Stream m (Array a) -> D.Stream m (Array a)
621−packArraysChunksOf n (D.Stream step state) =
622− D.Stream step' (SpliceInitial state)
593+-------------------------------------------------------------------------------
594+-- to Lists and streams
595+-------------------------------------------------------------------------------
623596
597+-- Use foldr/build fusion to fuse with list consumers
598+-- This can be useful when using the IsList instance
599+{-# INLINE_LATE toListFB #-}
600+toListFB :: forall a b. Storable a => (a -> b -> b) -> b -> Array a -> b
601+toListFB c n Array{..} = go (unsafeForeignPtrToPtr aStart)
624602 where
625603
626− {-# INLINE_LATE step' #-}
627− step' gst (SpliceInitial st) = do
628− when (n <= 0) $
629− -- XXX we can pass the module string from the higher level API
630− error $ "Streamly.Internal.Data.Array.Storable.Foreign.Mut.Types.packArraysChunksOf: the size of "
631− ++ "arrays [" ++ show n ++ "] must be a natural number"
632− r <- step gst st
633− case r of
634− D.Yield arr s -> return $
635− let len = byteLength arr
636− in if len >= n
637− then D.Skip (SpliceYielding arr (SpliceInitial s))
638− else D.Skip (SpliceBuffering s arr)
639− D.Skip s -> return $ D.Skip (SpliceInitial s)
640− D.Stop -> return D.Stop
641−
642− step' gst (SpliceBuffering st buf) = do
643− r <- step gst st
644− case r of
645− D.Yield arr s -> do
646− let len = byteLength buf + byteLength arr
647− if len > n
648− then return $
649− D.Skip (SpliceYielding buf (SpliceBuffering s arr))
650− else do
651− buf' <- if byteCapacity buf < n
652− then liftIO $ realloc n buf
653− else return buf
654− buf'' <- spliceWith buf' arr
655− return $ D.Skip (SpliceBuffering s buf'')
656− D.Skip s -> return $ D.Skip (SpliceBuffering s buf)
657− D.Stop -> return $ D.Skip (SpliceYielding buf SpliceFinish)
658−
659− step' _ SpliceFinish = return D.Stop
660−
661− step' _ (SpliceYielding arr next) = return $ D.Yield arr next
662−
663−-- XXX instead of writing two different versions of this operation, we should
664−-- write it as a pipe.
665−{-# INLINE_NORMAL lpackArraysChunksOf #-}
666−lpackArraysChunksOf :: (MonadIO m, Storable a)
667− => Int -> Fold m (Array a) () -> Fold m (Array a) ()
668−lpackArraysChunksOf n (Fold step1 initial1 extract1) =
669− Fold step initial extract
604+ go p | p == aEnd = n
605+ go p =
606+ -- unsafeInlineIO allows us to run this in Identity monad for pure
607+ -- toList/foldr case which makes them much faster due to not
608+ -- accumulating the list and fusing better with the pure consumers.
609+ --
610+ -- This should be safe as the array contents are guaranteed to be
611+ -- evaluated/written to before we peek at them.
612+ let !x = unsafeInlineIO $ do
613+ r <- peek p
614+ touchForeignPtr aStart
615+ return r
616+ in c x (go (p `plusPtr` sizeOf (undefined :: a)))
670617
671− where
618+-- | Convert an 'Array' into a list.
619+--
620+-- @since 0.7.0
621+{-# INLINE toList #-}
622+toList :: Storable a => Array a -> [a]
623+toList s = build (\c n -> toListFB c n s)
672624
673− initial = do
674− when (n <= 0) $
675− -- XXX we can pass the module string from the higher level API
676− error $ "Streamly.Internal.Data.Array.Storable.Foreign.Mut.Types.packArraysChunksOf: the size of "
677− ++ "arrays [" ++ show n ++ "] must be a natural number"
678−
679− r <- initial1
680− return $ first (Tuple' Nothing) r
681−
682− extract (Tuple' Nothing r1) = extract1 r1
683− extract (Tuple' (Just buf) r1) = do
684− r <- step1 r1 buf
685− case r of
686− FL.Partial rr -> extract1 rr
687− FL.Done _ -> return ()
688−
689− step (Tuple' Nothing r1) arr =
690− let len = byteLength arr
691− in if len >= n
692− then do
693− r <- step1 r1 arr
694− case r of
695− FL.Done _ -> return $ FL.Done ()
696− FL.Partial s -> do
697− extract1 s
698− res <- initial1
699− return $ first (Tuple' Nothing) res
700− else return $ FL.Partial $ Tuple' (Just arr) r1
701−
702− step (Tuple' (Just buf) r1) arr = do
703− let len = byteLength buf + byteLength arr
704− buf' <- if byteCapacity buf < len
705− then liftIO $ realloc (max n len) buf
706− else return buf
707− buf'' <- spliceWith buf' arr
708−
709− -- XXX this is common in both the equations of step
710− if len >= n
711− then do
712− r <- step1 r1 buf''
713− case r of
714− FL.Done _ -> return $ FL.Done ()
715− FL.Partial s -> do
716− extract1 s
717− res <- initial1
718− return $ first (Tuple' Nothing) res
719− else return $ FL.Partial $ Tuple' (Just buf'') r1
720−
721−#if !defined(mingw32_HOST_OS)
722−data GatherState s arr
723− = GatherInitial s
724− | GatherBuffering s arr Int
725− | GatherYielding arr (GatherState s arr)
726− | GatherFinish
727−
728−-- | @groupIOVecsOf maxBytes maxEntries@ groups arrays in the incoming stream
729−-- to create a stream of 'IOVec' arrays with a maximum of @maxBytes@ bytes in
730−-- each array and a maximum of @maxEntries@ entries in each array.
731−--
732−-- @since 0.7.0
733−{-# INLINE_NORMAL groupIOVecsOf #-}
734−groupIOVecsOf :: MonadIO m
735− => Int -> Int -> D.Stream m (Array a) -> D.Stream m (Array IOVec)
736−groupIOVecsOf n maxIOVLen (D.Stream step state) =
737− D.Stream step' (GatherInitial state)
738−
739− where
740−
741− {-# INLINE_LATE step' #-}
742− step' gst (GatherInitial st) = do
743− when (n <= 0) $
744− -- XXX we can pass the module string from the higher level API
745− error $ "Streamly.Internal.Data.Array.Storable.Foreign.Mut.Types.groupIOVecsOf: the size of "
746− ++ "groups [" ++ show n ++ "] must be a natural number"
747− when (maxIOVLen <= 0) $
748− -- XXX we can pass the module string from the higher level API
749− error $ "Streamly.Internal.Data.Array.Storable.Foreign.Mut.Types.groupIOVecsOf: the number of "
750− ++ "IOVec entries [" ++ show n ++ "] must be a natural number"
751− r <- step (adaptState gst) st
752− case r of
753− D.Yield arr s -> do
754− let p = unsafeForeignPtrToPtr (aStart arr)
755− len = byteLength arr
756− iov <- liftIO $ newArray maxIOVLen
757− iov' <- liftIO $ unsafeSnoc iov (IOVec (castPtr p)
758− (fromIntegral len))
759− if len >= n
760− then return $ D.Skip (GatherYielding iov' (GatherInitial s))
761− else return $ D.Skip (GatherBuffering s iov' len)
762− D.Skip s -> return $ D.Skip (GatherInitial s)
763− D.Stop -> return D.Stop
764−
765− step' gst (GatherBuffering st iov len) = do
766− r <- step (adaptState gst) st
767− case r of
768− D.Yield arr s -> do
769− let p = unsafeForeignPtrToPtr (aStart arr)
770− alen = byteLength arr
771− len' = len + alen
772− if len' > n || length iov >= maxIOVLen
773− then do
774− iov' <- liftIO $ newArray maxIOVLen
775− iov'' <- liftIO $ unsafeSnoc iov' (IOVec (castPtr p)
776− (fromIntegral alen))
777− return $ D.Skip (GatherYielding iov
778− (GatherBuffering s iov'' alen))
779− else do
780− iov' <- liftIO $ unsafeSnoc iov (IOVec (castPtr p)
781− (fromIntegral alen))
782− return $ D.Skip (GatherBuffering s iov' len')
783− D.Skip s -> return $ D.Skip (GatherBuffering s iov len)
784− D.Stop -> return $ D.Skip (GatherYielding iov GatherFinish)
785−
786− step' _ GatherFinish = return D.Stop
787−
788− step' _ (GatherYielding iov next) = return $ D.Yield iov next
789−#endif
790−
791−
792−data SplitState s arr
793− = Initial s
794− | Buffering s arr
795− | Splitting s arr
796− | Yielding arr (SplitState s arr)
797− | Finishing
798−
799−-- | Split a stream of arrays on a given separator byte, dropping the separator
800−-- and coalescing all the arrays between two separators into a single array.
801−--
802−-- @since 0.7.0
803−{-# INLINE_NORMAL splitOn #-}
804−splitOn
805− :: MonadIO m
806− => Word8
807− -> D.Stream m (Array Word8)
808− -> D.Stream m (Array Word8)
809−splitOn byte (D.Stream step state) = D.Stream step' (Initial state)
810−
811− where
812−
813− {-# INLINE_LATE step' #-}
814− step' gst (Initial st) = do
815− r <- step gst st
816− case r of
817− D.Yield arr s -> do
818− (arr1, marr2) <- breakOn byte arr
819− return $ case marr2 of
820− Nothing -> D.Skip (Buffering s arr1)
821− Just arr2 -> D.Skip (Yielding arr1 (Splitting s arr2))
822− D.Skip s -> return $ D.Skip (Initial s)
823− D.Stop -> return D.Stop
824−
825− step' gst (Buffering st buf) = do
826− r <- step gst st
827− case r of
828− D.Yield arr s -> do
829− (arr1, marr2) <- breakOn byte arr
830− buf' <- spliceTwo buf arr1
831− return $ case marr2 of
832− Nothing -> D.Skip (Buffering s buf')
833− Just x -> D.Skip (Yielding buf' (Splitting s x))
834− D.Skip s -> return $ D.Skip (Buffering s buf)
835− D.Stop -> return $
836− if byteLength buf == 0
837− then D.Stop
838− else D.Skip (Yielding buf Finishing)
839−
840− step' _ (Splitting st buf) = do
841− (arr1, marr2) <- breakOn byte buf
842− return $ case marr2 of
843− Nothing -> D.Skip $ Buffering st arr1
844− Just arr2 -> D.Skip $ Yielding arr1 (Splitting st arr2)
845−
846− step' _ (Yielding arr next) = return $ D.Yield arr next
847− step' _ Finishing = return D.Stop
848−
849−
850−-- Drops the separator byte
851−{-# INLINE breakOn #-}
852−breakOn :: MonadIO m
853− => Word8 -> Array Word8 -> m (Array Word8, Maybe (Array Word8))
854−breakOn sep arr@Array{..} = liftIO $ do
855− let p = unsafeForeignPtrToPtr aStart
856− loc <- c_memchr p sep (fromIntegral $ aEnd `minusPtr` p)
857− return $
858− if loc == nullPtr
859− then (arr, Nothing)
860− else
861− ( Array
862− { aStart = aStart
863− , aEnd = loc
864− , aBound = loc
865− }
866− , Just $ Array
867− { aStart = aStart `plusForeignPtr` (loc `minusPtr` p + 1)
868− , aEnd = aEnd
869− , aBound = aBound
870− }
871− )
872−
873−-------------------------------------------------------------------------------
874−-- Elimination
875−-------------------------------------------------------------------------------
876−
877−-- | Return element at the specified index without checking the bounds.
625+-- | Use the 'read' unfold instead.
878626--
879−-- Unsafe because it does not check the bounds of the array.
880−{-# INLINE_NORMAL unsafeIndexIO #-}
881−unsafeIndexIO :: forall a. Storable a => Array a -> Int -> IO a
882−unsafeIndexIO Array {..} i =
883− withForeignPtr aStart $ \p -> do
884− let elemSize = sizeOf (undefined :: a)
885− elemOff = p `plusPtr` (elemSize * i)
886− assert (i >= 0 && elemOff `plusPtr` elemSize <= aEnd)
887− (return ())
888− peek elemOff
889−
890−-- | Return element at the specified index without checking the bounds.
891−{-# INLINE_NORMAL unsafeIndex #-}
892−unsafeIndex :: forall a. Storable a => Array a -> Int -> a
893−unsafeIndex arr i = let !r = unsafeInlineIO $ unsafeIndexIO arr i in r
894−
895−-- | /O(1)/ Get the byte length of the array.
896−--
897−-- @since 0.7.0
898−{-# INLINE byteLength #-}
899−byteLength :: Array a -> Int
900−byteLength Array{..} =
901− let p = unsafeForeignPtrToPtr aStart
902− len = aEnd `minusPtr` p
903− in assert (len >= 0) len
904−
905−-- | /O(1)/ Get the length of the array i.e. the number of elements in the
906−-- array.
627+-- @toStreamD = D.unfold read@
907628--
908−-- @since 0.7.0
909−{-# INLINE length #-}
910−length :: forall a. Storable a => Array a -> Int
911−length arr = byteLength arr `div` sizeOf (undefined :: a)
912−
913−{-# INLINE byteCapacity #-}
914−byteCapacity :: Array a -> Int
915−byteCapacity Array{..} =
916− let p = unsafeForeignPtrToPtr aStart
917− len = aBound `minusPtr` p
918− in assert (len >= 0) len
919−
920−-- Use foldr/build fusion to fuse with list consumers
921−-- This can be useful when using the IsList instance
922−{-# INLINE_LATE toListFB #-}
923−toListFB :: forall a b. Storable a => (a -> b -> b) -> b -> Array a -> b
924−toListFB c n Array{..} = go (unsafeForeignPtrToPtr aStart)
925− where
926−
927− go p | p == aEnd = n
928− go p =
929− -- unsafeInlineIO allows us to run this in Identity monad for pure
930− -- toList/foldr case which makes them much faster due to not
931− -- accumulating the list and fusing better with the pure consumers.
932− --
933− -- This should be safe as the array contents are guaranteed to be
934− -- evaluated/written to before we peek at them.
935− let !x = unsafeInlineIO $ do
936− r <- peek p
937− touchForeignPtr aStart
938− return r
939− in c x (go (p `plusPtr` sizeOf (undefined :: a)))
940−
941−-- | Convert an 'Array' into a list.
942−--
943−-- @since 0.7.0
944−{-# INLINE toList #-}
945−toList :: Storable a => Array a -> [a]
946−toList s = build (\c n -> toListFB c n s)
947−
629+-- We can try this if the unfold has any performance issues.
948630{-# INLINE_NORMAL toStreamD #-}
949631toStreamD :: forall m a. (Monad m, Storable a) => Array a -> D.Stream m a
950632toStreamD Array{..} =
@@ -1019,70 +701,22 @@ toStreamKRev Array {..} =
1019701 return r
1020702 in x `K.cons` go (p `plusPtr` negate (sizeOf (undefined :: a)))
1021703
704+-------------------------------------------------------------------------------
705+-- Folding
706+-------------------------------------------------------------------------------
707+
708+-- | Strict left fold of an array.
1022709{-# INLINE_NORMAL foldl' #-}
1023710foldl' :: forall a b. Storable a => (b -> a -> b) -> b -> Array a -> b
1024711foldl' f z arr = runIdentity $ D.foldl' f z $ toStreamD arr
1025712
713+-- | Right fold of an array.
1026714{-# INLINE_NORMAL foldr #-}
1027715foldr :: Storable a => (a -> b -> b) -> b -> Array a -> b
1028716foldr f z arr = runIdentity $ D.foldr f z $ toStreamD arr
1029717
1030−
1031−-- | Create two slices of an array without copying the original array. The
1032−-- specified index @i@ is the first index of the second slice.
1033−--
1034−-- @since 0.7.0
1035−splitAt :: forall a. Storable a => Int -> Array a -> (Array a, Array a)
1036−splitAt i arr@Array{..} =
1037− let maxIndex = length arr - 1
1038− in if i < 0
1039− then error "sliceAt: negative array index"
1040− else if i > maxIndex
1041− then error $ "sliceAt: specified array index " ++ show i
1042− ++ " is beyond the maximum index " ++ show maxIndex
1043− else let off = i * sizeOf (undefined :: a)
1044− p = unsafeForeignPtrToPtr aStart `plusPtr` off
1045− in ( Array
1046− { aStart = aStart
1047− , aEnd = p
1048− , aBound = p
1049− }
1050− , Array
1051− { aStart = aStart `plusForeignPtr` off
1052− , aEnd = aEnd
1053− , aBound = aBound
1054− }
1055− )
1056−
1057−
1058−{-# INLINE_NORMAL unlines #-}
1059−unlines :: forall m a. (MonadIO m, Storable a)
1060− => a -> D.Stream m (Array a) -> D.Stream m a
1061−unlines sep (D.Stream step state) = D.Stream step' (OuterLoop state)
1062− where
1063− {-# INLINE_LATE step' #-}
1064− step' gst (OuterLoop st) = do
1065− r <- step (adaptState gst) st
1066− return $ case r of
1067− D.Yield Array{..} s ->
1068− let p = unsafeForeignPtrToPtr aStart
1069− in D.Skip (InnerLoop s aStart p aEnd)
1070− D.Skip s -> D.Skip (OuterLoop s)
1071− D.Stop -> D.Stop
1072−
1073− step' _ (InnerLoop st _ p end) | p == end =
1074− return $ D.Yield sep $ OuterLoop st
1075−
1076− step' _ (InnerLoop st startf p end) = do
1077− x <- liftIO $ do
1078− r <- peek p
1079− touchForeignPtr startf
1080− return r
1081− return $ D.Yield x (InnerLoop st startf
1082− (p `plusPtr` sizeOf (undefined :: a)) end)
1083−
1084718-------------------------------------------------------------------------------
1085−-- Folds
719+-- Write Folds to fold streams into arrays
1086720-------------------------------------------------------------------------------
1087721
1088722{-# INLINE_NORMAL writeNAllocWith #-}
@@ -1104,6 +738,8 @@ writeNAllocWith alloc n = Fold step initial extract
1104738-- | @writeN n@ folds a maximum of @n@ elements from the input stream to an
1105739-- 'Array'.
1106740--
741+-- @writeN n = Fold.takeLE n writeNUnsafe@
742+--
1107743-- @since 0.7.0
1108744{-# INLINE_NORMAL writeN #-}
1109745writeN :: forall m a. (MonadIO m, Storable a) => Int -> Fold m a (Array a)
@@ -1162,6 +798,26 @@ writeNUnsafe n = Fold step initial extract
1162798
1163799 extract (ArrayUnsafe start end) = return $ Array start end end -- liftIO . shrinkToFit
1164800
801+-- XXX Buffer to a list instead?
802+--
803+-- | Buffer a stream into a stream of arrays.
804+--
805+-- @writeChunks = Fold.many Fold.toStream (Array.writeN n)@
806+--
807+-- See 'bufferChunks'.
808+--
809+-- /Unimplemented/
810+--
811+{-# INLINE_NORMAL writeChunks #-}
812+writeChunks :: -- (MonadIO m, Storable a) =>
813+ Int -> Fold m a (D.Stream m (Array a))
814+writeChunks = undefined -- Fold.many Fold.toStream (Array.writeN n)
815+
816+-- XXX Compare toArrayMinChunk with fromStreamD which uses an array of streams
817+-- implementation. We can write this using writeChunks above if that is faster.
818+-- If toArrayMinChunk is faster then we should use that to implement
819+-- fromStreamD.
820+--
1165821-- XXX The realloc based implementation needs to make one extra copy if we use
1166822-- shrinkToFit. On the other hand, the stream of arrays implementation may
1167823-- buffer the array chunk pointers in memory but it does not have to shrink as
@@ -1224,6 +880,184 @@ writeAligned alignSize =
1224880 (bytesToElemCount (undefined :: a)
1225881 (mkChunkSize 1024))
1226882
883+-------------------------------------------------------------------------------
884+-- construct from streams, known size
885+-------------------------------------------------------------------------------
886+
887+-- | Use the 'writeN' fold instead.
888+--
889+-- @fromStreamDN n = D.fold (writeN n)@
890+--
891+{-# INLINE_NORMAL fromStreamDN #-}
892+fromStreamDN :: forall m a. (MonadIO m, Storable a)
893+ => Int -> D.Stream m a -> m (Array a)
894+fromStreamDN limit str = do
895+ arr <- liftIO $ newArray limit
896+ end <- D.foldlM' fwrite (return $ aEnd arr) $ D.take limit str
897+ return $ arr {aEnd = end}
898+
899+ where
900+
901+ fwrite ptr x = do
902+ liftIO $ poke ptr x
903+ return $ ptr `plusPtr` sizeOf (undefined :: a)
904+
905+-- | Create an 'Array' from the first N elements of a list. The array is
906+-- allocated to size N, if the list terminates before N elements then the
907+-- array may hold less than N elements.
908+--
909+-- @since 0.7.0
910+{-# INLINABLE fromListN #-}
911+fromListN :: Storable a => Int -> [a] -> Array a
912+fromListN n xs = unsafePerformIO $ fromStreamDN n $ D.fromList xs
913+
914+-------------------------------------------------------------------------------
915+-- convert stream to a single array
916+-------------------------------------------------------------------------------
917+
918+-- CAUTION: a very large number (millions) of arrays can degrade performance
919+-- due to GC overhead because we need to buffer the arrays before we flatten
920+-- all the arrays.
921+--
922+-- XXX Compare if this is faster or "fold write".
923+--
924+-- | We could take the approach of doubling the memory allocation on each
925+-- overflow. This would result in more or less the same amount of copying as in
926+-- the chunking approach. However, if we have to shrink in the end then it may
927+-- result in an extra copy of the entire data.
928+--
929+-- @
930+-- fromStreamD = StreamD.fold Array.write
931+-- @
932+--
933+{-# INLINE fromStreamD #-}
934+fromStreamD :: (MonadIO m, Storable a) => D.Stream m a -> m (Array a)
935+fromStreamD m = do
936+ buffered <- bufferChunks m
937+ len <- K.foldl' (+) 0 (K.map length buffered)
938+ fromStreamDN len $ D.concatUnfold read $ D.fromStreamK buffered
939+
940+-- | Create an 'Array' from a list. The list must be of finite size.
941+--
942+-- @since 0.7.0
943+{-# INLINABLE fromList #-}
944+fromList :: Storable a => [a] -> Array a
945+fromList xs = unsafePerformIO $ fromStreamD $ D.fromList xs
946+
947+-------------------------------------------------------------------------------
948+-- Combining
949+-------------------------------------------------------------------------------
950+
951+-- | Copy two arrays into a newly allocated array.
952+{-# INLINE spliceTwo #-}
953+spliceTwo :: (MonadIO m, Storable a) => Array a -> Array a -> m (Array a)
954+spliceTwo arr1 arr2 = do
955+ let src1 = unsafeForeignPtrToPtr (aStart arr1)
956+ src2 = unsafeForeignPtrToPtr (aStart arr2)
957+ len1 = aEnd arr1 `minusPtr` src1
958+ len2 = aEnd arr2 `minusPtr` src2
959+
960+ arr <- liftIO $ newArray (len1 + len2)
961+ let dst = unsafeForeignPtrToPtr (aStart arr)
962+
963+ -- XXX Should we use copyMutableByteArray# instead? Is there an overhead to
964+ -- ccall?
965+ liftIO $ do
966+ memcpy (castPtr dst) (castPtr src1) len1
967+ touchForeignPtr (aStart arr1)
968+ memcpy (castPtr (dst `plusPtr` len1)) (castPtr src2) len2
969+ touchForeignPtr (aStart arr2)
970+ return arr { aEnd = dst `plusPtr` (len1 + len2) }
971+
972+-- | Splice an array into a pre-reserved mutable array. The user must ensure
973+-- that there is enough space in the mutable array, otherwise the splicing
974+-- fails.
975+{-# INLINE spliceWith #-}
976+spliceWith :: (MonadIO m) => Array a -> Array a -> m (Array a)
977+spliceWith dst@(Array _ end bound) src =
978+ liftIO $ do
979+ let srcLen = byteLength src
980+ if end `plusPtr` srcLen > bound
981+ then error
982+ "Bug: spliceWith: Not enough space in the target array"
983+ else withForeignPtr (aStart dst) $ \_ ->
984+ withForeignPtr (aStart src) $ \psrc -> do
985+ let pdst = aEnd dst
986+ memcpy (castPtr pdst) (castPtr psrc) srcLen
987+ return $ dst {aEnd = pdst `plusPtr` srcLen}
988+
989+-- | Splice a new array into a preallocated mutable array, doubling the space
990+-- if there is no space in the target array.
991+{-# INLINE spliceWithDoubling #-}
992+spliceWithDoubling :: (MonadIO m, Storable a)
993+ => Array a -> Array a -> m (Array a)
994+spliceWithDoubling dst@(Array start end bound) src = do
995+ assert (end <= bound) (return ())
996+ let srcLen = aEnd src `minusPtr` unsafeForeignPtrToPtr (aStart src)
997+
998+ dst1 <-
999+ if end `plusPtr` srcLen >= bound
1000+ then do
1001+ let oldStart = unsafeForeignPtrToPtr start
1002+ oldSize = end `minusPtr` oldStart
1003+ newSize = max (oldSize * 2) (oldSize + srcLen)
1004+ liftIO $ realloc newSize dst
1005+ else return dst
1006+ spliceWith dst1 src
1007+
1008+-------------------------------------------------------------------------------
1009+-- Splitting
1010+-------------------------------------------------------------------------------
1011+
1012+-- | Drops the separator byte
1013+{-# INLINE breakOn #-}
1014+breakOn :: MonadIO m
1015+ => Word8 -> Array Word8 -> m (Array Word8, Maybe (Array Word8))
1016+breakOn sep arr@Array{..} = liftIO $ do
1017+ let p = unsafeForeignPtrToPtr aStart
1018+ loc <- c_memchr p sep (fromIntegral $ aEnd `minusPtr` p)
1019+ return $
1020+ if loc == nullPtr
1021+ then (arr, Nothing)
1022+ else
1023+ ( Array
1024+ { aStart = aStart
1025+ , aEnd = loc
1026+ , aBound = loc
1027+ }
1028+ , Just $ Array
1029+ { aStart = aStart `plusForeignPtr` (loc `minusPtr` p + 1)
1030+ , aEnd = aEnd
1031+ , aBound = aBound
1032+ }
1033+ )
1034+
1035+-- | Create two slices of an array without copying the original array. The
1036+-- specified index @i@ is the first index of the second slice.
1037+--
1038+-- @since 0.7.0
1039+splitAt :: forall a. Storable a => Int -> Array a -> (Array a, Array a)
1040+splitAt i arr@Array{..} =
1041+ let maxIndex = length arr - 1
1042+ in if i < 0
1043+ then error "sliceAt: negative array index"
1044+ else if i > maxIndex
1045+ then error $ "sliceAt: specified array index " ++ show i
1046+ ++ " is beyond the maximum index " ++ show maxIndex
1047+ else let off = i * sizeOf (undefined :: a)
1048+ p = unsafeForeignPtrToPtr aStart `plusPtr` off
1049+ in ( Array
1050+ { aStart = aStart
1051+ , aEnd = p
1052+ , aBound = p
1053+ }
1054+ , Array
1055+ { aStart = aStart `plusForeignPtr` off
1056+ , aEnd = aEnd
1057+ , aBound = aBound
1058+ }
1059+ )
1060+
12271061-------------------------------------------------------------------------------
12281062-- Instances
12291063-------------------------------------------------------------------------------
@@ -1346,6 +1180,9 @@ instance Foldable Array where
13461180-- Semigroup and Monoid
13471181-------------------------------------------------------------------------------
13481182
1183+-- Note: we cannot splice the second array into the free space of the first
1184+-- array because that would require a monadic API due to mutation.
1185+-- | Copies the two arrays into a newly allocated array.
13491186instance Storable a => Semigroup (Array a) where
13501187 {-# INLINE (<>) #-}
13511188 arr1 <> arr2 = unsafePerformIO $ spliceTwo arr1 arr2
src/Streamly/Internal/Data/Array/Storable/Foreign/Types.hs+36−111
@@ -9,8 +9,11 @@
99-- Stability : experimental
1010-- Portability : GHC
1111--
12+-- See notes in "Streamly.Internal.Data.Array.Storable.Foreign.Mut.Types"
13+--
1214module Streamly.Internal.Data.Array.Storable.Foreign.Types
1315 (
16+ -- $arrayNotes
1417 Array (..)
1518
1619 -- * Freezing and Thawing
@@ -27,19 +30,9 @@ module Streamly.Internal.Data.Array.Storable.Foreign.Types
2730 , fromList
2831 , fromListN
2932 , fromStreamDN
33+ , fromStreamD
3034
31− -- * Streams of arrays
32− , fromStreamDArraysOf
33− , MA.FlattenState (..) -- for inspection testing
34− , flattenArrays
35− , flattenArraysRev
36− , packArraysChunksOf
37− , MA.SpliceState (..)
38− , lpackArraysChunksOf
39−#if !defined(mingw32_HOST_OS)
40− , groupIOVecsOf
41−#endif
42− , splitOn
35+ -- * Split
4336 , breakOn
4437
4538 -- * Elimination
@@ -60,8 +53,6 @@ module Streamly.Internal.Data.Array.Storable.Foreign.Types
6053 , toStreamRev
6154 , toList
6255
63− , unlines
64−
6556 -- * Folds
6657 , toArrayMinChunk
6758 , writeN
@@ -72,6 +63,12 @@ module Streamly.Internal.Data.Array.Storable.Foreign.Types
7263 , write
7364 , writeAligned
7465
66+ -- * Streams of arrays
67+ , arraysOf
68+ , bufferChunks
69+ , flattenArrays
70+ , flattenArraysRev
71+
7572 -- * Utilities
7673 , MA.defaultChunkSize
7774 , MA.mkChunkSize
@@ -104,70 +101,31 @@ import Text.Read (readPrec, readListPrec, readListPrecDefault)
104101
105102import Prelude hiding (length, foldr, read, unlines, splitAt)
106103
104+import qualified Streamly.Internal.Data.Array.Storable.Foreign.Mut.Types as MA
107105import qualified Streamly.Internal.Data.Stream.StreamD.Type as D
108106import qualified Streamly.Internal.Data.Stream.StreamK.Type as K
109−import qualified Streamly.Internal.Data.Array.Storable.Foreign.Mut.Types as MA
110−import qualified Streamly.Internal.Data.Fold.Types as Fold
111107import qualified GHC.Exts as Exts
112108
113109#if __GLASGOW_HASKELL__ < 808
114110import Data.Semigroup (Semigroup(..))
115111#endif
116112
117−#if !defined(mingw32_HOST_OS)
118−import Streamly.FileSystem.FDIO (IOVec(..))
119−#endif
120−
121113#ifdef DEVBUILD
122114import qualified Data.Foldable as F
123115#endif
124116
125−-------------------------------------------------------------------------------
126−-- Design Notes
127−-------------------------------------------------------------------------------
128−
129−-- There are two goals that we want to fulfill with arrays. One, holding large
130−-- amounts of data in non-GC memory and the ability to pass this data to and
131−-- from the operating system without an extra copy overhead. Two, allow random
132−-- access to elements based on their indices. The first one falls in the
133−-- category of storage buffers while the second one falls in the category of
134−-- maps/multisets/hashmaps.
135−--
136−-- For the first requirement we use an array of Storables and store it in a
137−-- ForeignPtr. We can have both immutable and mutable variants of this array
138−-- using wrappers over the same underlying type.
139−--
140−-- For the second requirement, we need a separate type for arrays of
141−-- polymorphic values, for example vectors of handler functions, lookup tables.
142−-- We can call this type a "vector" in contrast to arrays. It should not
143−-- require Storable instance for the type. In that case we need to use an
144−-- Array# instead of a ForeignPtr. This type of array would not reduce the GC
145−-- overhead much because each element of the array still needs to be scanned by
146−-- the GC. However, it can store polymorphic elements and allow random access
147−-- to those. But in most cases random access means storage, and it means we
148−-- need to avoid GC scanning except in cases of trivially small storage. One
149−-- way to achieve that would be to put the array in a Compact region. However,
150−-- if and when we mutate this, we will have to use a manual GC copying out to
151−-- another CR and freeing the old one.
152−
153117-------------------------------------------------------------------------------
154118-- Array Data Type
155119-------------------------------------------------------------------------------
156120
157−-- We require that an array stores only Storable. Arrays are used for buffering
158−-- data while streams are used for processing. If you want something to be
159−-- buffered it better be Storable so that we can store it in non-GC memory.
121+-- $arrayNotes
160122--
161−-- We can use a "Storable" constraint in the Array type and the Constraint can
123+-- We can use a 'Storable' constraint in the Array type and the constraint can
162124-- be automatically provided to a function that pattern matches on the Array
163125-- type. However, it has huge performance cost, so we do not use it.
164−-- XXX Can this cost be alleviated in GHC-8.10 specialization fix?
126+-- Investigate a GHC improvement possiblity.
165127--
166−-- XXX Another way to not require the Storable constraint in array operations
167−-- is to store the elemSize in the array at construction and use that instead
168−-- of using sizeOf. Need to charaterize perf cost of this.
169−--
170−-- XXX rename the fields to "start, next, end".
128+-- XXX Rename the fields to better names.
171129--
172130data Array a =
173131#ifdef DEVBUILD
@@ -343,20 +301,33 @@ fromStreamDN :: forall m a. (MonadIO m, Storable a)
343301 => Int -> D.Stream m a -> m (Array a)
344302fromStreamDN limit str = unsafeFreeze <$> MA.fromStreamDN limit str
345303
304+{-# INLINE_NORMAL fromStreamD #-}
305+fromStreamD :: forall m a. (MonadIO m, Storable a)
306+ => D.Stream m a -> m (Array a)
307+fromStreamD str = unsafeFreeze <$> MA.fromStreamD str
308+
346309-------------------------------------------------------------------------------
347310-- Streams of arrays
348311-------------------------------------------------------------------------------
349312
350−-- | @fromStreamArraysOf n stream@ groups the input stream into a stream of
313+{-# INLINE bufferChunks #-}
314+bufferChunks :: (MonadIO m, Storable a) =>
315+ D.Stream m a -> m (K.Stream m (Array a))
316+bufferChunks m = D.foldr K.cons K.nil $ arraysOf MA.defaultChunkSize m
317+
318+-- | @arraysOf n stream@ groups the input stream into a stream of
351319-- arrays of size n.
352−{-# INLINE_NORMAL fromStreamDArraysOf #-}
353−fromStreamDArraysOf :: forall m a. (MonadIO m, Storable a)
320+{-# INLINE_NORMAL arraysOf #-}
321+arraysOf :: forall m a. (MonadIO m, Storable a)
354322 => Int -> D.Stream m a -> D.Stream m (Array a)
355−-- fromStreamDArraysOf n str = D.groupsOf n (writeN n) str
356−fromStreamDArraysOf n str = D.map unsafeFreeze $ MA.fromStreamDArraysOf n str
323+arraysOf n str = D.map unsafeFreeze $ MA.arraysOf n str
357324
358−-- XXX concatMap does not seem to have the best possible performance so we have
359−-- a custom way to concat arrays.
325+-- | Use the "read" unfold instead.
326+--
327+-- @flattenArrays = concatUnfold read@
328+--
329+-- We can try this if there are any fusion issues in the unfold.
330+--
360331{-# INLINE_NORMAL flattenArrays #-}
361332flattenArrays :: forall m a. (MonadIO m, Storable a)
362333 => D.Stream m (Array a) -> D.Stream m a
@@ -367,47 +338,6 @@ flattenArraysRev :: forall m a. (MonadIO m, Storable a)
367338 => D.Stream m (Array a) -> D.Stream m a
368339flattenArraysRev = MA.flattenArraysRev . D.map unsafeThaw
369340
370−
371−{-# INLINE_NORMAL packArraysChunksOf #-}
372−packArraysChunksOf :: (MonadIO m, Storable a)
373− => Int -> D.Stream m (Array a) -> D.Stream m (Array a)
374−packArraysChunksOf n str =
375− D.map unsafeFreeze $ MA.packArraysChunksOf n $ D.map unsafeThaw str
376−
377−-- XXX instead of writing two different versions of this operation, we should
378−-- write it as a pipe.
379−{-# INLINE_NORMAL lpackArraysChunksOf #-}
380−lpackArraysChunksOf :: (MonadIO m, Storable a)
381− => Int -> Fold m (Array a) () -> Fold m (Array a) ()
382−lpackArraysChunksOf n fld =
383− Fold.map unsafeThaw $ MA.lpackArraysChunksOf n (Fold.map unsafeFreeze fld)
384−
385−#if !defined(mingw32_HOST_OS)
386−
387−-- | @groupIOVecsOf maxBytes maxEntries@ groups arrays in the incoming stream
388−-- to create a stream of 'IOVec' arrays with a maximum of @maxBytes@ bytes in
389−-- each array and a maximum of @maxEntries@ entries in each array.
390−--
391−-- @since 0.7.0
392−{-# INLINE_NORMAL groupIOVecsOf #-}
393−groupIOVecsOf :: MonadIO m
394− => Int -> Int -> D.Stream m (Array a) -> D.Stream m (Array IOVec)
395−groupIOVecsOf n maxIOVLen str =
396− D.map unsafeFreeze $ MA.groupIOVecsOf n maxIOVLen $ D.map unsafeThaw str
397−#endif
398−
399−-- | Split a stream of arrays on a given separator byte, dropping the separator
400−-- and coalescing all the arrays between two separators into a single array.
401−--
402−-- @since 0.7.0
403−{-# INLINE_NORMAL splitOn #-}
404−splitOn
405− :: MonadIO m
406− => Word8
407− -> D.Stream m (Array Word8)
408− -> D.Stream m (Array Word8)
409−splitOn byte str = D.map unsafeFreeze $ MA.splitOn byte $ D.map unsafeThaw str
410−
411341-- Drops the separator byte
412342{-# INLINE breakOn #-}
413343breakOn :: MonadIO m
@@ -508,11 +438,6 @@ splitAt i arr = (unsafeFreeze a, unsafeFreeze b)
508438toList :: Storable a => Array a -> [a]
509439toList = MA.toList . unsafeThaw
510440
511−{-# INLINE_NORMAL unlines #-}
512−unlines :: forall m a. (MonadIO m, Storable a)
513− => a -> D.Stream m (Array a) -> D.Stream m a
514−unlines sep str = MA.unlines sep (D.map unsafeThaw str)
515−
516441-------------------------------------------------------------------------------
517442-- Folds
518443-------------------------------------------------------------------------------
src/Streamly/Internal/Data/Array/Stream/Mut/Foreign.hsnew file+311
@@ -0,0 +1,311 @@
1+-- |
2+-- Module : Streamly.Internal.Data.Array.Stream.Mut.Foreign
3+-- Copyright : (c) 2019 Composewell Technologies
4+-- License : BSD3-3-Clause
5+-- Maintainer : streamly@composewell.com
6+-- Stability : experimental
7+-- Portability : GHC
8+--
9+-- Combinators to efficiently manipulate streams of mutable arrays.
10+--
11+module Streamly.Internal.Data.Array.Stream.Mut.Foreign
12+ (
13+ -- * Generation
14+ arraysOf
15+
16+ -- * Compaction
17+ , packArraysChunksOf
18+ , SpliceState (..)
19+ , lpackArraysChunksOf
20+#if !defined(mingw32_HOST_OS)
21+ , groupIOVecsOf
22+#endif
23+ , compact
24+ , compactLE
25+ , compactEQ
26+ , compactGE
27+ )
28+where
29+
30+#include "inline.hs"
31+
32+import Control.Monad.IO.Class (MonadIO(..))
33+import Control.Monad (when)
34+import Data.Bifunctor (first)
35+import Foreign.Storable (Storable(..))
36+#if !defined(mingw32_HOST_OS)
37+import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
38+import Foreign.Ptr (castPtr)
39+import Streamly.FileSystem.FDIO (IOVec(..))
40+import Streamly.Internal.Data.Array.Storable.Foreign.Mut.Types (length)
41+import Streamly.Internal.Data.SVar (adaptState)
42+#endif
43+import Streamly.Internal.Data.Array.Storable.Foreign.Mut.Types (Array(..))
44+import Streamly.Internal.Data.Fold.Types (Fold(..))
45+import Streamly.Internal.Data.Stream.Serial (SerialT)
46+import Streamly.Internal.Data.Stream.StreamK.Type (IsStream)
47+import Streamly.Internal.Data.Tuple.Strict (Tuple'(..))
48+
49+import qualified Streamly.Internal.Data.Array.Storable.Foreign.Mut.Types as MArray
50+import qualified Streamly.Internal.Data.Fold.Types as FL
51+import qualified Streamly.Internal.Data.Stream.StreamD as D
52+
53+import Prelude hiding (length)
54+
55+-- | @arraysOf n stream@ groups the elements in the input stream into arrays of
56+-- @n@ elements each.
57+--
58+-- Same as the following but may be more efficient:
59+--
60+-- > arraysOf n = Stream.foldMany (MArray.writeN n)
61+--
62+-- /Internal/
63+{-# INLINE arraysOf #-}
64+arraysOf :: (IsStream t, MonadIO m, Storable a)
65+ => Int -> t m a -> t m (Array a)
66+arraysOf n = D.fromStreamD . MArray.arraysOf n . D.toStreamD
67+
68+-------------------------------------------------------------------------------
69+-- Compact
70+-------------------------------------------------------------------------------
71+
72+data SpliceState s arr
73+ = SpliceInitial s
74+ | SpliceBuffering s arr
75+ | SpliceYielding arr (SpliceState s arr)
76+ | SpliceFinish
77+
78+-- | This mutates the first array (if it has space) to append values from the
79+-- second one. This would work for immutable arrays as well because an
80+-- immutable array never has space so a new array is allocated instead of
81+-- mutating it.
82+--
83+-- | Coalesce adjacent arrays in incoming stream to form bigger arrays of a
84+-- maximum specified size. Note that if a single array is bigger than the
85+-- specified size we do not split it to fit. When we coalesce multiple arrays
86+-- if the size would exceed the specified size we do not coalesce therefore the
87+-- actual array size may be less than the specified chunk size.
88+--
89+-- @since 0.7.0
90+{-# INLINE_NORMAL packArraysChunksOf #-}
91+packArraysChunksOf :: (MonadIO m, Storable a)
92+ => Int -> D.Stream m (Array a) -> D.Stream m (Array a)
93+packArraysChunksOf n (D.Stream step state) =
94+ D.Stream step' (SpliceInitial state)
95+
96+ where
97+
98+ {-# INLINE_LATE step' #-}
99+ step' gst (SpliceInitial st) = do
100+ when (n <= 0) $
101+ -- XXX we can pass the module string from the higher level API
102+ error $ "Streamly.Internal.Data.Array.Storable.Foreign.Mut.Types.packArraysChunksOf: the size of "
103+ ++ "arrays [" ++ show n ++ "] must be a natural number"
104+ r <- step gst st
105+ case r of
106+ D.Yield arr s -> return $
107+ let len = MArray.byteLength arr
108+ in if len >= n
109+ then D.Skip (SpliceYielding arr (SpliceInitial s))
110+ else D.Skip (SpliceBuffering s arr)
111+ D.Skip s -> return $ D.Skip (SpliceInitial s)
112+ D.Stop -> return D.Stop
113+
114+ step' gst (SpliceBuffering st buf) = do
115+ r <- step gst st
116+ case r of
117+ D.Yield arr s -> do
118+ let len = MArray.byteLength buf + MArray.byteLength arr
119+ if len > n
120+ then return $
121+ D.Skip (SpliceYielding buf (SpliceBuffering s arr))
122+ else do
123+ buf' <- if MArray.byteCapacity buf < n
124+ then liftIO $ MArray.realloc n buf
125+ else return buf
126+ buf'' <- MArray.spliceWith buf' arr
127+ return $ D.Skip (SpliceBuffering s buf'')
128+ D.Skip s -> return $ D.Skip (SpliceBuffering s buf)
129+ D.Stop -> return $ D.Skip (SpliceYielding buf SpliceFinish)
130+
131+ step' _ SpliceFinish = return D.Stop
132+
133+ step' _ (SpliceYielding arr next) = return $ D.Yield arr next
134+
135+{-# INLINE_NORMAL lpackArraysChunksOf #-}
136+lpackArraysChunksOf :: (MonadIO m, Storable a)
137+ => Int -> Fold m (Array a) () -> Fold m (Array a) ()
138+lpackArraysChunksOf n (Fold step1 initial1 extract1) =
139+ Fold step initial extract
140+
141+ where
142+
143+ initial = do
144+ when (n <= 0) $
145+ -- XXX we can pass the module string from the higher level API
146+ error $ "Streamly.Internal.Data.Array.Storable.Foreign.Mut.Types.packArraysChunksOf: the size of "
147+ ++ "arrays [" ++ show n ++ "] must be a natural number"
148+
149+ r <- initial1
150+ return $ first (Tuple' Nothing) r
151+
152+ extract (Tuple' Nothing r1) = extract1 r1
153+ extract (Tuple' (Just buf) r1) = do
154+ r <- step1 r1 buf
155+ case r of
156+ FL.Partial rr -> extract1 rr
157+ FL.Done _ -> return ()
158+
159+ step (Tuple' Nothing r1) arr =
160+ let len = MArray.byteLength arr
161+ in if len >= n
162+ then do
163+ r <- step1 r1 arr
164+ case r of
165+ FL.Done _ -> return $ FL.Done ()
166+ FL.Partial s -> do
167+ extract1 s
168+ res <- initial1
169+ return $ first (Tuple' Nothing) res
170+ else return $ FL.Partial $ Tuple' (Just arr) r1
171+
172+ step (Tuple' (Just buf) r1) arr = do
173+ let len = MArray.byteLength buf + MArray.byteLength arr
174+ buf' <- if MArray.byteCapacity buf < len
175+ then liftIO $ MArray.realloc (max n len) buf
176+ else return buf
177+ buf'' <- MArray.spliceWith buf' arr
178+
179+ -- XXX this is common in both the equations of step
180+ if len >= n
181+ then do
182+ r <- step1 r1 buf''
183+ case r of
184+ FL.Done _ -> return $ FL.Done ()
185+ FL.Partial s -> do
186+ extract1 s
187+ res <- initial1
188+ return $ first (Tuple' Nothing) res
189+ else return $ FL.Partial $ Tuple' (Just buf'') r1
190+
191+-- XXX replace by compactLE
192+--
193+-- | Coalesce adjacent arrays in incoming stream to form bigger arrays of a
194+-- maximum specified size in bytes.
195+--
196+-- @since 0.7.0
197+{-# INLINE compact #-}
198+compact :: (MonadIO m, Storable a)
199+ => Int -> SerialT m (Array a) -> SerialT m (Array a)
200+compact n xs = D.fromStreamD $ packArraysChunksOf n (D.toStreamD xs)
201+
202+-- XXX Replace the above functions by a compactLEFold
203+-- | Coalesce adjacent arrays in incoming stream to form bigger arrays of a
204+-- maximum specified size. Note that if a single array is bigger than the
205+-- specified size we do not split it to fit. When we coalesce multiple arrays
206+-- if the size would exceed the specified size we do not coalesce therefore the
207+-- actual array size may be less than the specified chunk size.
208+--
209+-- /Unimplemented/
210+{-# INLINE_NORMAL compactLEFold #-}
211+compactLEFold :: -- (MonadIO m, Storable a) =>
212+ Int -> Fold m (Array a) (Array a)
213+compactLEFold = undefined
214+
215+compactLE :: (MonadIO m {-, Storable a-}) =>
216+ Int -> SerialT m (Array a) -> SerialT m (Array a)
217+compactLE n xs = D.fromStreamD $ D.foldMany (compactLEFold n) (D.toStreamD xs)
218+
219+-- | Like 'compact' but generates arrays of exactly equal to the size specified
220+-- except for the last array in the stream which could be shorter.
221+--
222+-- /Unimplemented/
223+{-# INLINE compactEQ #-}
224+compactEQ :: -- (MonadIO m, Storable a) =>
225+ Int -> SerialT m (Array a) -> SerialT m (Array a)
226+compactEQ _n _xs = undefined
227+ -- D.fromStreamD $ D.foldMany (compactEQFold n) (D.toStreamD xs)
228+
229+-- | Like 'compact' but generates arrays of size greater than or equal to the
230+-- specified except for the last array in the stream which could be shorter.
231+--
232+-- /Unimplemented/
233+{-# INLINE compactGE #-}
234+compactGE :: -- (MonadIO m, Storable a) =>
235+ Int -> SerialT m (Array a) -> SerialT m (Array a)
236+compactGE _n _xs = undefined
237+ -- D.fromStreamD $ D.foldMany (compactGEFold n) (D.toStreamD xs)
238+
239+-------------------------------------------------------------------------------
240+-- IOVec
241+-------------------------------------------------------------------------------
242+
243+#if !defined(mingw32_HOST_OS)
244+data GatherState s arr
245+ = GatherInitial s
246+ | GatherBuffering s arr Int
247+ | GatherYielding arr (GatherState s arr)
248+ | GatherFinish
249+
250+-- | @groupIOVecsOf maxBytes maxEntries@ groups arrays in the incoming stream
251+-- to create a stream of 'IOVec' arrays with a maximum of @maxBytes@ bytes in
252+-- each array and a maximum of @maxEntries@ entries in each array.
253+--
254+-- @since 0.7.0
255+{-# INLINE_NORMAL groupIOVecsOf #-}
256+groupIOVecsOf :: MonadIO m
257+ => Int -> Int -> D.Stream m (Array a) -> D.Stream m (Array IOVec)
258+groupIOVecsOf n maxIOVLen (D.Stream step state) =
259+ D.Stream step' (GatherInitial state)
260+
261+ where
262+
263+ {-# INLINE_LATE step' #-}
264+ step' gst (GatherInitial st) = do
265+ when (n <= 0) $
266+ -- XXX we can pass the module string from the higher level API
267+ error $ "Streamly.Internal.Data.Array.Storable.Foreign.Mut.Types.groupIOVecsOf: the size of "
268+ ++ "groups [" ++ show n ++ "] must be a natural number"
269+ when (maxIOVLen <= 0) $
270+ -- XXX we can pass the module string from the higher level API
271+ error $ "Streamly.Internal.Data.Array.Storable.Foreign.Mut.Types.groupIOVecsOf: the number of "
272+ ++ "IOVec entries [" ++ show n ++ "] must be a natural number"
273+ r <- step (adaptState gst) st
274+ case r of
275+ D.Yield arr s -> do
276+ let p = unsafeForeignPtrToPtr (aStart arr)
277+ len = MArray.byteLength arr
278+ iov <- liftIO $ MArray.newArray maxIOVLen
279+ iov' <- liftIO $ MArray.unsafeSnoc iov (IOVec (castPtr p)
280+ (fromIntegral len))
281+ if len >= n
282+ then return $ D.Skip (GatherYielding iov' (GatherInitial s))
283+ else return $ D.Skip (GatherBuffering s iov' len)
284+ D.Skip s -> return $ D.Skip (GatherInitial s)
285+ D.Stop -> return D.Stop
286+
287+ step' gst (GatherBuffering st iov len) = do
288+ r <- step (adaptState gst) st
289+ case r of
290+ D.Yield arr s -> do
291+ let p = unsafeForeignPtrToPtr (aStart arr)
292+ alen = MArray.byteLength arr
293+ len' = len + alen
294+ if len' > n || length iov >= maxIOVLen
295+ then do
296+ iov' <- liftIO $ MArray.newArray maxIOVLen
297+ iov'' <- liftIO $ MArray.unsafeSnoc iov' (IOVec (castPtr p)
298+ (fromIntegral alen))
299+ return $ D.Skip (GatherYielding iov
300+ (GatherBuffering s iov'' alen))
301+ else do
302+ iov' <- liftIO $ MArray.unsafeSnoc iov (IOVec (castPtr p)
303+ (fromIntegral alen))
304+ return $ D.Skip (GatherBuffering s iov' len')
305+ D.Skip s -> return $ D.Skip (GatherBuffering s iov len)
306+ D.Stop -> return $ D.Skip (GatherYielding iov GatherFinish)
307+
308+ step' _ GatherFinish = return D.Stop
309+
310+ step' _ (GatherYielding iov next) = return $ D.Yield iov next
311+#endif
src/Streamly/Internal/Data/Stream/IsStream/Nesting.hs+9−8
@@ -254,8 +254,7 @@ import Foreign.Storable (Storable)
254254import Streamly.Internal.Data.Fold.Types (Fold (..), Fold2 (..))
255255import Streamly.Internal.Data.Parser (Parser (..))
256256import Streamly.Internal.Data.Unfold.Types (Unfold)
257−import Streamly.Internal.Data.Array.Storable.Foreign.Types
258− (Array, writeNUnsafe)
257+import Streamly.Internal.Data.Array.Storable.Foreign.Types (Array)
259258import Streamly.Internal.Data.SVar (MonadAsync)
260259import Streamly.Internal.Data.Stream.Ahead (ahead)
261260import Streamly.Internal.Data.Stream.Async (async, wAsync)
@@ -650,7 +649,7 @@ concatMapEitherWith = undefined
650649-- @since 0.7.0
651650{-# INLINE concatUnfold #-}
652651concatUnfold ::(IsStream t, Monad m) => Unfold m a b -> t m a -> t m b
653−concatUnfold u m = fromStreamD $ D.concatMapU u (toStreamD m)
652+concatUnfold u m = fromStreamD $ D.concatUnfold u (toStreamD m)
654653
655654-- | Like 'concatUnfold' but interleaves the streams in the same way as
656655-- 'interleave' behaves instead of appending them.
@@ -749,7 +748,7 @@ gintercalate unf1 str1 unf2 str2 =
749748intercalate :: (IsStream t, Monad m)
750749 => b -> Unfold m b c -> t m b -> t m c
751750intercalate seed unf str = D.fromStreamD $
752− D.concatMapU unf $ D.intersperse seed (toStreamD str)
751+ D.concatUnfold unf $ D.intersperse seed (toStreamD str)
753752
754753-- | 'interleaveSuffix' followed by unfold and concat.
755754--
@@ -777,7 +776,7 @@ gintercalateSuffix unf1 str1 unf2 str2 =
777776{-# INLINE intercalateSuffix #-}
778777intercalateSuffix :: (IsStream t, Monad m)
779778 => b -> Unfold m b c -> t m b -> t m c
780−intercalateSuffix seed unf str = fromStreamD $ D.concatMapU unf
779+intercalateSuffix seed unf str = fromStreamD $ D.concatUnfold unf
781780 $ D.intersperseSuffix (return seed) (D.toStreamD str)
782781
783782------------------------------------------------------------------------------
@@ -1570,12 +1569,14 @@ splitOnSuffixSeqAny subseq f m = undefined
15701569-- This can be considered as an n-fold version of 'takeLE' where we apply
15711570-- 'takeLE' repeatedly on the leftover stream until the stream exhausts.
15721571--
1572+-- @chunksOf n f = foldMany (FL.takeLE n f)@
1573+--
15731574-- @since 0.7.0
15741575{-# INLINE chunksOf #-}
15751576chunksOf
15761577 :: (IsStream t, Monad m)
15771578 => Int -> Fold m a b -> t m a -> t m b
1578−chunksOf n f = foldMany (FL.takeLE n f)
1579+chunksOf n f = D.fromStreamD . D.chunksOf n f . D.toStreamD
15791580
15801581-- |
15811582--
@@ -1591,13 +1592,13 @@ chunksOf2 n action f m = D.fromStreamD $ D.groupsOf2 n action f (D.toStreamD m)
15911592--
15921593-- Same as the following but may be more efficient:
15931594--
1594−-- > arraysOf n = S.chunksOf n (A.writeN n)
1595+-- > arraysOf n = Stream.foldMany (A.writeN n)
15951596--
15961597-- /Internal/
15971598{-# INLINE arraysOf #-}
15981599arraysOf :: (IsStream t, MonadIO m, Storable a)
15991600 => Int -> t m a -> t m (Array a)
1600−arraysOf n = chunksOf n (writeNUnsafe n)
1601+arraysOf n = D.fromStreamD . A.arraysOf n . D.toStreamD
16011602
16021603-- XXX we can implement this by repeatedly applying the 'lrunFor' fold.
16031604-- XXX add this example after fixing the serial stream rate control
src/Streamly/Internal/Data/Stream/StreamD/Generate.hs−22
@@ -97,7 +97,6 @@ import Streamly.Internal.Data.Time.Units (toRelTime64, RelTime64)
9797import Streamly.Internal.Data.Time.Clock (Clock(Monotonic), getTime)
9898import Streamly.Internal.Data.Time.Units
9999 (MicroSecond64(..), fromAbsTime, toAbsTime, AbsTime)
100−import Streamly.Internal.Data.Unfold.Types (Unfold(..))
101100
102101import qualified Streamly.Internal.Data.IORef.Prim as Prim
103102
@@ -132,27 +131,6 @@ cons x (Stream step state) = Stream step1 Nothing
132131 Skip s -> Skip (Just s)
133132 Stop -> Stop
134133
135−------------------------------------------------------------------------------
136−-- From 'Unfold'
137−------------------------------------------------------------------------------
138−
139−data UnfoldState s = UnfoldNothing | UnfoldJust s
140−
141−-- | Convert an 'Unfold' into a 'Stream' by supplying it a seed.
142−--
143−{-# INLINE_NORMAL unfold #-}
144−unfold :: Monad m => Unfold m a b -> a -> Stream m b
145−unfold (Unfold ustep inject) seed = Stream step UnfoldNothing
146− where
147− {-# INLINE_LATE step #-}
148− step _ UnfoldNothing = inject seed >>= return . Skip . UnfoldJust
149− step _ (UnfoldJust st) = do
150− r <- ustep st
151− return $ case r of
152− Yield x s -> Yield x (UnfoldJust s)
153− Skip s -> Skip (UnfoldJust s)
154− Stop -> Stop
155−
156134------------------------------------------------------------------------------
157135-- Unfolding
158136------------------------------------------------------------------------------
src/Streamly/Internal/Data/Stream/StreamD/Nesting.hs+2−38
@@ -83,8 +83,7 @@ module Streamly.Internal.Data.Stream.StreamD.Nesting
8383 -- | Generate streams by using an unfold on each element of an input
8484 -- stream, append the resulting streams and flatten. A special case of
8585 -- gintercalate.
86− , ConcatMapUState (..)
87− , concatMapU
86+ , concatUnfold
8887 , ConcatUnfoldInterleaveState (..)
8988 , concatUnfoldInterleave
9089 , concatUnfoldRoundrobin
@@ -125,6 +124,7 @@ module Streamly.Internal.Data.Stream.StreamD.Nesting
125124
126125 -- ** Grouping
127126 -- | Group segments of a stream and fold. Special case of parsing.
127+ , chunksOf
128128 , groupsOf2
129129 , groupsBy
130130 , groupsRollingBy
@@ -479,42 +479,6 @@ mergeBy cmp = mergeByM (\a b -> return $ cmp a b)
479479-- Combine N Streams - concatUnfold
480480------------------------------------------------------------------------------
481481
482−-- Define a unique structure to use in inspection testing
483−data ConcatMapUState o i =
484− ConcatMapUOuter o
485− | ConcatMapUInner o i
486−
487−-- | @concatMapU unfold stream@ uses @unfold@ to map the input stream elements
488−-- to streams and then flattens the generated streams into a single output
489−-- stream.
490−
491−-- This is like 'concatMap' but uses an unfold with an explicit state to
492−-- generate the stream instead of a 'Stream' type generator. This allows better
493−-- optimization via fusion. This can be many times more efficient than
494−-- 'concatMap'.
495−
496−{-# INLINE_NORMAL concatMapU #-}
497−concatMapU :: Monad m => Unfold m a b -> Stream m a -> Stream m b
498−concatMapU (Unfold istep inject) (Stream ostep ost) =
499− Stream step (ConcatMapUOuter ost)
500− where
501− {-# INLINE_LATE step #-}
502− step gst (ConcatMapUOuter o) = do
503− r <- ostep (adaptState gst) o
504− case r of
505− Yield a o' -> do
506− i <- inject a
507− i `seq` return (Skip (ConcatMapUInner o' i))
508− Skip o' -> return $ Skip (ConcatMapUOuter o')
509− Stop -> return $ Stop
510−
511− step _ (ConcatMapUInner o i) = do
512− r <- istep i
513− return $ case r of
514− Yield x i' -> Yield x (ConcatMapUInner o i')
515− Skip i' -> Skip (ConcatMapUInner o i')
516− Stop -> Skip (ConcatMapUOuter o)
517−
518482data ConcatUnfoldInterleaveState o i =
519483 ConcatUnfoldInterleaveOuter o [i]
520484 | ConcatUnfoldInterleaveInner o [i]
src/Streamly/Internal/Data/Stream/StreamD/Step.hsnew file+39
@@ -0,0 +1,39 @@
1+-- |
2+-- Module : Streamly.Internal.Data.Stream.StreamD.Step
3+-- Copyright : (c) 2018 Composewell Technologies
4+-- License : BSD-3-Clause
5+-- Maintainer : streamly@composewell.com
6+-- Stability : experimental
7+-- Portability : GHC
8+
9+module Streamly.Internal.Data.Stream.StreamD.Step
10+ (
11+ -- * The stream type
12+ Step (..)
13+ )
14+where
15+
16+import Fusion.Plugin.Types (Fuse(..))
17+
18+-- | A stream is a succession of 'Step's. A 'Yield' produces a single value and
19+-- the next state of the stream. 'Stop' indicates there are no more values in
20+-- the stream.
21+{-# ANN type Step Fuse #-}
22+data Step s a = Yield a s | Skip s | Stop
23+
24+instance Functor (Step s) where
25+ {-# INLINE fmap #-}
26+ fmap f (Yield x s) = Yield (f x) s
27+ fmap _ (Skip s) = Skip s
28+ fmap _ Stop = Stop
29+
30+{-
31+yield :: Monad m => a -> s -> m (Step s a)
32+yield a = return . Yield a
33+
34+skip :: Monad m => s -> m (Step s a)
35+skip = return . Skip
36+
37+stop :: Monad m => m (Step s a)
38+stop = return Stop
39+-}
src/Streamly/Internal/Data/Stream/StreamD/Transform.hs+13−13
@@ -1102,11 +1102,11 @@ reverse m = Stream step Nothing
11021102reverse' :: forall m a. (MonadIO m, Storable a) => Stream m a -> Stream m a
11031103{-
11041104-- This commented implementation copies the whole stream into one single array
1105−-- and then streams from that array, this is 3-4x faster than the chunked code
1106−-- that follows. Though this could be problematic due to unbounded large
1107−-- allocations. We need to figure out why the chunked code is slower and if we
1108−-- can optimize the chunked code to work as fast as this one. It may be a
1109−-- fusion issue?
1105+-- and then streams from that array, this has exactly the same performance as
1106+-- the chunked code that follows. Though this could be problematic due to
1107+-- unbounded large allocations. However, if we use an idiomatic implementation
1108+-- of arraysOf instead of the custom implementation then the chunked code
1109+-- becomes worse by 6 times. Need to investigate if that can be improved.
11101110import Foreign.ForeignPtr (touchForeignPtr)
11111111import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
11121112import Foreign.Ptr (Ptr, plusPtr)
@@ -1115,8 +1115,8 @@ reverse' m = Stream step Nothing
11151115 {-# INLINE_LATE step #-}
11161116 step _ Nothing = do
11171117 arr <- A.fromStreamD m
1118− let p = aEnd arr `plusPtr` negate (sizeOf (undefined :: a))
1119− return $ Skip $ Just (aStart arr, p)
1118+ let p = A.aEnd arr `plusPtr` negate (sizeOf (undefined :: a))
1119+ return $ Skip $ Just (A.aStart arr, p)
11201120
11211121 step _ (Just (start, p)) | p < unsafeForeignPtrToPtr start = return Stop
11221122
@@ -1128,12 +1128,12 @@ reverse' m = Stream step Nothing
11281128 next = p `plusPtr` negate (sizeOf (undefined :: a))
11291129 return $ Yield x (Just (start, next))
11301130-}
1131−reverse' m =
1132− A.flattenArraysRev
1133− $ fromStreamK
1134− $ K.reverse
1135− $ toStreamK
1136− $ A.fromStreamDArraysOf A.defaultChunkSize m
1131+reverse' =
1132+ A.flattenArraysRev -- concatUnfold A.readRev
1133+ . fromStreamK
1134+ . K.reverse
1135+ . toStreamK
1136+ . A.arraysOf A.defaultChunkSize
11371137
11381138------------------------------------------------------------------------------
11391139-- Position Indexing
src/Streamly/Internal/Data/Stream/StreamD/Type.hs+77−13
@@ -26,6 +26,9 @@ module Streamly.Internal.Data.Stream.StreamD.Type
2626 , consM
2727 , uncons
2828
29+ -- * From Unfold
30+ , unfold
31+
2932 -- * From Values
3033 , yield
3134 , yieldM
@@ -70,12 +73,15 @@ module Streamly.Internal.Data.Stream.StreamD.Type
7073 , takeWhileM
7174
7275 -- * Nesting
76+ , ConcatMapUState (..)
77+ , concatUnfold
7378 , concatMap
7479 , concatMapM
7580 , GroupState (..) -- for inspection testing
7681 , foldMany
7782 , foldMany1
7883 , groupsOf2
84+ , chunksOf
7985 )
8086where
8187
@@ -90,7 +96,9 @@ import GHC.Types (SPEC(..))
9096import Prelude hiding (map, mapM, foldr, take, concatMap, takeWhile)
9197
9298import Streamly.Internal.Data.Fold.Types (Fold(..), Fold2(..))
99+import Streamly.Internal.Data.Stream.StreamD.Step (Step (..))
93100import Streamly.Internal.Data.SVar (State, adaptState, defState)
101+import Streamly.Internal.Data.Unfold.Types (Unfold(..))
94102
95103import qualified Streamly.Internal.Data.Fold.Types as FL
96104import qualified Streamly.Internal.Data.Stream.StreamK.Type as K
@@ -99,18 +107,6 @@ import qualified Streamly.Internal.Data.Stream.StreamK.Type as K
99107-- The direct style stream type
100108------------------------------------------------------------------------------
101109
102−-- | A stream is a succession of 'Step's. A 'Yield' produces a single value and
103−-- the next state of the stream. 'Stop' indicates there are no more values in
104−-- the stream.
105−{-# ANN type Step Fuse #-}
106−data Step s a = Yield a s | Skip s | Stop
107−
108−instance Functor (Step s) where
109− {-# INLINE fmap #-}
110− fmap f (Yield x s) = Yield (f x) s
111− fmap _ (Skip s) = Skip s
112− fmap _ Stop = Stop
113−
114110-- gst = global state
115111-- | A stream consists of a step function that generates the next step given a
116112-- current state, and the current state.
@@ -164,6 +160,27 @@ uncons (UnStream step state) = go state
164160 Skip s -> go s
165161 Stop -> return Nothing
166162
163+------------------------------------------------------------------------------
164+-- From 'Unfold'
165+------------------------------------------------------------------------------
166+
167+data UnfoldState s = UnfoldNothing | UnfoldJust s
168+
169+-- | Convert an 'Unfold' into a 'Stream' by supplying it a seed.
170+--
171+{-# INLINE_NORMAL unfold #-}
172+unfold :: Monad m => Unfold m a b -> a -> Stream m b
173+unfold (Unfold ustep inject) seed = Stream step UnfoldNothing
174+ where
175+ {-# INLINE_LATE step #-}
176+ step _ UnfoldNothing = inject seed >>= return . Skip . UnfoldJust
177+ step _ (UnfoldJust st) = do
178+ r <- ustep st
179+ return $ case r of
180+ Yield x s -> Yield x (UnfoldJust s)
181+ Skip s -> Skip (UnfoldJust s)
182+ Stop -> Stop
183+
167184------------------------------------------------------------------------------
168185-- From Values
169186------------------------------------------------------------------------------
@@ -204,6 +221,7 @@ fromList = Stream step
204221-- Conversions From/To
205222------------------------------------------------------------------------------
206223
224+-- | Convert a CPS encoded StreamK to direct style step encoded StreamD
207225{-# INLINE_LATE fromStreamK #-}
208226fromStreamK :: Monad m => K.Stream m a -> Stream m a
209227fromStreamK = Stream step
@@ -214,7 +232,7 @@ fromStreamK = Stream step
214232 yieldk a r = return $ Yield a r
215233 in K.foldStreamShared gst yieldk single stop m1
216234
217−-- Convert a direct stream to and from CPS encoded stream
235+-- | Convert a direct style step encoded StreamD to a CPS encoded StreamK
218236{-# INLINE_LATE toStreamK #-}
219237toStreamK :: Monad m => Stream m a -> K.Stream m a
220238toStreamK (Stream step state) = go state
@@ -235,10 +253,12 @@ toStreamK (Stream step state) = go state
235253 forall s. fromStreamK (toStreamK s) = s #-}
236254#endif
237255
256+-- XXX Rename to toStream or move to some IsStream common module
238257{-# INLINE fromStreamD #-}
239258fromStreamD :: (K.IsStream t, Monad m) => Stream m a -> t m a
240259fromStreamD = K.fromStream . toStreamK
241260
261+-- XXX Rename to toStream or move to some IsStream common module
242262{-# INLINE toStreamD #-}
243263toStreamD :: (K.IsStream t, Monad m) => t m a -> Stream m a
244264toStreamD = fromStreamK . K.toStream
@@ -659,6 +679,46 @@ instance Applicative f => Applicative (Stream f) where
659679 {-# INLINE (<*) #-}
660680 (<*) = apDiscardSnd
661681
682+------------------------------------------------------------------------------
683+-- Combine N Streams - concatUnfold
684+------------------------------------------------------------------------------
685+
686+-- Define a unique structure to use in inspection testing
687+data ConcatMapUState o i =
688+ ConcatMapUOuter o
689+ | ConcatMapUInner o i
690+
691+-- | @concatUnfold unfold stream@ uses @unfold@ to map the input stream elements
692+-- to streams and then flattens the generated streams into a single output
693+-- stream.
694+
695+-- This is like 'concatMap' but uses an unfold with an explicit state to
696+-- generate the stream instead of a 'Stream' type generator. This allows better
697+-- optimization via fusion. This can be many times more efficient than
698+-- 'concatMap'.
699+
700+{-# INLINE_NORMAL concatUnfold #-}
701+concatUnfold :: Monad m => Unfold m a b -> Stream m a -> Stream m b
702+concatUnfold (Unfold istep inject) (Stream ostep ost) =
703+ Stream step (ConcatMapUOuter ost)
704+ where
705+ {-# INLINE_LATE step #-}
706+ step gst (ConcatMapUOuter o) = do
707+ r <- ostep (adaptState gst) o
708+ case r of
709+ Yield a o' -> do
710+ i <- inject a
711+ i `seq` return (Skip (ConcatMapUInner o' i))
712+ Skip o' -> return $ Skip (ConcatMapUOuter o')
713+ Stop -> return $ Stop
714+
715+ step _ (ConcatMapUInner o i) = do
716+ r <- istep i
717+ return $ case r of
718+ Yield x i' -> Yield x (ConcatMapUInner o i')
719+ Skip i' -> Skip (ConcatMapUInner o i')
720+ Stop -> Skip (ConcatMapUOuter o)
721+
662722------------------------------------------------------------------------------
663723-- Combine N Streams - concatMap
664724------------------------------------------------------------------------------
@@ -843,6 +903,10 @@ foldMany1 (Fold fstep initial extract) (Stream step state) =
843903 step' _ (FoldMany1Yield b next) = return $ Yield b next
844904 step' _ FoldMany1Done = return Stop
845905
906+{-# INLINE chunksOf #-}
907+chunksOf :: Monad m => Int -> Fold m a b -> Stream m a -> Stream m b
908+chunksOf n f = foldMany (FL.takeLE n f)
909+
846910data GroupState2 s fs
847911 = GroupStart2 s
848912 | GroupBuffer2 s fs Int
src/Streamly/Internal/Data/Unfold.hs+2−13
@@ -159,16 +159,16 @@ import Streamly.Internal.Data.IOFinalizer
159159 (newIOFinalizer, runIOFinalizer, clearingIOFinalizer)
160160import Streamly.Internal.Data.Stream.StreamD.Type (Stream(..), Step(..))
161161import Streamly.Internal.Data.Time.Clock (Clock(Monotonic), getTime)
162−import Streamly.Internal.Data.Unfold.Types (Unfold(..))
162+import Streamly.Internal.Data.Unfold.Types (Unfold(..), lmap)
163163import System.Mem (performMajorGC)
164164
165165import qualified Prelude
166166import qualified Control.Monad.Catch as MC
167167import qualified Data.Tuple as Tuple
168168import qualified Streamly.Internal.Data.Fold.Types as FL
169+import qualified Streamly.Internal.Data.Stream.StreamD.Type as D
169170import qualified Streamly.Internal.Data.Stream.StreamK as K (uncons)
170171import qualified Streamly.Internal.Data.Stream.StreamK.Type as K
171−import qualified Streamly.Internal.Data.Stream.StreamD as D (unfold)
172172
173173import Streamly.Internal.Data.SVar
174174import Prelude
@@ -179,17 +179,6 @@ import Prelude
179179-- Input operations
180180-------------------------------------------------------------------------------
181181
182−-- | Map a function on the input argument of the 'Unfold'.
183−--
184−-- @
185−-- lmap f = concat (singleton f)
186−-- @
187−--
188−-- /Internal/
189−{-# INLINE_NORMAL lmap #-}
190−lmap :: (a -> c) -> Unfold m c b -> Unfold m a b
191−lmap f (Unfold ustep uinject) = Unfold ustep (uinject . f)
192−
193182-- | Map an action on the input argument of the 'Unfold'.
194183--
195184-- @
src/Streamly/Internal/Data/Unfold/Types.hs+15−1
@@ -8,10 +8,13 @@
88
99module Streamly.Internal.Data.Unfold.Types
1010 ( Unfold (..)
11+ , lmap
1112 )
1213where
1314
14−import Streamly.Internal.Data.Stream.StreamD.Type (Step(..))
15+#include "inline.hs"
16+
17+import Streamly.Internal.Data.Stream.StreamD.Step (Step(..))
1518
1619------------------------------------------------------------------------------
1720-- Monadic Unfolds
@@ -25,3 +28,14 @@ import Streamly.Internal.Data.Stream.StreamD.Type (Step(..))
2528data Unfold m a b =
2629 -- | @Unfold step inject@
2730 forall s. Unfold (s -> m (Step s b)) (a -> m s)
31+
32+-- | Map a function on the input argument of the 'Unfold'.
33+--
34+-- @
35+-- lmap f = concat (singleton f)
36+-- @
37+--
38+-- /Internal/
39+{-# INLINE_NORMAL lmap #-}
40+lmap :: (a -> c) -> Unfold m c b -> Unfold m a b
41+lmap f (Unfold ustep uinject) = Unfold ustep (uinject . f)
src/Streamly/Internal/FileSystem/Handle.hs+2−2
@@ -114,11 +114,11 @@ import Streamly.Data.Fold (Fold)
114114import Streamly.Internal.Data.Fold.Types (Fold2(..))
115115import Streamly.Internal.Data.Unfold.Types (Unfold(..))
116116import Streamly.Internal.Data.Array.Storable.Foreign.Types
117− (Array(..), writeNUnsafe, defaultChunkSize, lpackArraysChunksOf
118− , unsafeFreezeWithShrink)
117+ (Array(..), writeNUnsafe, defaultChunkSize , unsafeFreezeWithShrink)
119118import Streamly.Internal.Data.Array.Storable.Foreign.Mut.Types (mutableArray)
120119import Streamly.Internal.Data.Stream.Serial (SerialT)
121120import Streamly.Internal.Data.Stream.StreamK.Type (IsStream, mkStream)
121+import Streamly.Internal.Memory.ArrayStream (lpackArraysChunksOf)
122122-- import Streamly.String (encodeUtf8, decodeUtf8, foldLines)
123123
124124import qualified Streamly.Data.Fold as FL
src/Streamly/Internal/Memory/ArrayStream.hs+189−36
@@ -1,15 +1,12 @@
1−#include "inline.hs"
2−
31-- |
42-- Module : Streamly.Internal.Memory.ArrayStream
5−-- Copyright : (c) 2020 Composewell Technologies
6−--
3+-- Copyright : (c) 2019 Composewell Technologies
74-- License : BSD3-3-Clause
85-- Maintainer : streamly@composewell.com
96-- Stability : experimental
107-- Portability : GHC
118--
12−-- Combinators to efficiently manipulate streams of arrays.
9+-- Combinators to efficiently manipulate streams of immutable arrays.
1310--
1411module Streamly.Internal.Memory.ArrayStream
1512 (
@@ -22,35 +19,72 @@ module Streamly.Internal.Memory.ArrayStream
2219 , interpose
2320 , interposeSuffix
2421 , intercalateSuffix
25−
26− -- * Transformation
27− , splitOn
28− , splitOnSuffix
29− , compact -- compact
22+ , unlines
3023
3124 -- * Elimination
3225 , toArray
26+
27+ -- * Compaction
28+ , lpackArraysChunksOf
29+#if !defined(mingw32_HOST_OS)
30+ , groupIOVecsOf
31+#endif
32+ , compact
33+
34+ -- * Splitting
35+ , splitOn
36+ , splitOnSuffix
3337 )
3438where
3539
40+#include "inline.hs"
41+
3642import Control.Monad.IO.Class (MonadIO(..))
37−-- import Data.Functor.Identity (Identity)
3843import Data.Word (Word8)
39−import Foreign.ForeignPtr (withForeignPtr)
44+import Foreign.ForeignPtr (withForeignPtr, touchForeignPtr)
45+import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
4046import Foreign.Ptr (minusPtr, plusPtr, castPtr)
4147import Foreign.Storable (Storable(..))
42−import Prelude hiding (length, null, last, map, (!!), read, concat)
48+import GHC.ForeignPtr (ForeignPtr(..))
49+import GHC.Ptr (Ptr(..))
50+import Prelude hiding (length, null, last, map, (!!), read, concat, unlines)
4351
52+#if !defined(mingw32_HOST_OS)
53+import Streamly.FileSystem.FDIO (IOVec(..))
54+#endif
4455import Streamly.Internal.Data.Array.Storable.Foreign.Types (Array(..), length)
56+import Streamly.Internal.Data.Fold.Types (Fold(..))
4557import Streamly.Internal.Data.Stream.Serial (SerialT)
4658import Streamly.Internal.Data.Stream.StreamK.Type (IsStream)
59+import Streamly.Internal.Data.SVar (adaptState)
4760
4861import qualified Streamly.Internal.Data.Array.Storable.Foreign as A
4962import qualified Streamly.Internal.Data.Array.Storable.Foreign.Types as A
5063import qualified Streamly.Internal.Data.Array.Storable.Foreign.Mut.Types as MA
64+import qualified Streamly.Internal.Data.Array.Stream.Mut.Foreign as AS
65+import qualified Streamly.Internal.Data.Fold.Types as FL
5166import qualified Streamly.Internal.Data.Stream.IsStream as S
5267import qualified Streamly.Internal.Data.Stream.StreamD as D
5368
69+-------------------------------------------------------------------------------
70+-- Generation
71+-------------------------------------------------------------------------------
72+
73+-- | @arraysOf n stream@ groups the elements in the input stream into arrays of
74+-- @n@ elements each.
75+--
76+-- > arraysOf n = Stream.chunksOf n (Array.writeN n)
77+--
78+-- /Internal/
79+{-# INLINE arraysOf #-}
80+arraysOf :: (IsStream t, MonadIO m, Storable a)
81+ => Int -> t m a -> t m (Array a)
82+arraysOf n str = D.fromStreamD $ A.arraysOf n (D.toStreamD str)
83+
84+-------------------------------------------------------------------------------
85+-- Append
86+-------------------------------------------------------------------------------
87+
5488-- XXX efficiently compare two streams of arrays. Two streams can have chunks
5589-- of different sizes, we can handle that in the stream comparison abstraction.
5690-- This could be useful e.g. to fast compare whether two files differ.
@@ -66,7 +100,7 @@ import qualified Streamly.Internal.Data.Stream.StreamD as D
66100concat :: (IsStream t, MonadIO m, Storable a) => t m (Array a) -> t m a
67101-- concat m = D.fromStreamD $ A.flattenArrays (D.toStreamD m)
68102-- concat m = D.fromStreamD $ D.concatMap A.toStreamD (D.toStreamD m)
69−concat m = D.fromStreamD $ D.concatMapU A.read (D.toStreamD m)
103+concat m = D.fromStreamD $ D.concatUnfold A.read (D.toStreamD m)
70104
71105-- XXX should we have a reverseArrays API to reverse the stream of arrays
72106-- instead?
@@ -79,6 +113,10 @@ concat m = D.fromStreamD $ D.concatMapU A.read (D.toStreamD m)
79113concatRev :: (IsStream t, MonadIO m, Storable a) => t m (Array a) -> t m a
80114concatRev m = D.fromStreamD $ A.flattenArraysRev (D.toStreamD m)
81115
116+-------------------------------------------------------------------------------
117+-- Intersperse and append
118+-------------------------------------------------------------------------------
119+
82120-- | Flatten a stream of arrays after inserting the given element between
83121-- arrays.
84122--
@@ -102,6 +140,140 @@ interposeSuffix :: (MonadIO m, IsStream t, Storable a)
102140-- interposeSuffix x = D.fromStreamD . A.unlines x . D.toStreamD
103141interposeSuffix x = S.interposeSuffix x A.read
104142
143+data FlattenState s a =
144+ OuterLoop s
145+ | InnerLoop s !(ForeignPtr a) !(Ptr a) !(Ptr a)
146+
147+{-# INLINE_NORMAL unlines #-}
148+unlines :: forall m a. (MonadIO m, Storable a)
149+ => a -> D.Stream m (Array a) -> D.Stream m a
150+unlines sep (D.Stream step state) = D.Stream step' (OuterLoop state)
151+ where
152+ {-# INLINE_LATE step' #-}
153+ step' gst (OuterLoop st) = do
154+ r <- step (adaptState gst) st
155+ return $ case r of
156+ D.Yield Array{..} s ->
157+ let p = unsafeForeignPtrToPtr aStart
158+ in D.Skip (InnerLoop s aStart p aEnd)
159+ D.Skip s -> D.Skip (OuterLoop s)
160+ D.Stop -> D.Stop
161+
162+ step' _ (InnerLoop st _ p end) | p == end =
163+ return $ D.Yield sep $ OuterLoop st
164+
165+ step' _ (InnerLoop st startf p end) = do
166+ x <- liftIO $ do
167+ r <- peek p
168+ touchForeignPtr startf
169+ return r
170+ return $ D.Yield x (InnerLoop st startf
171+ (p `plusPtr` sizeOf (undefined :: a)) end)
172+
173+-------------------------------------------------------------------------------
174+-- Compact
175+-------------------------------------------------------------------------------
176+
177+{-# INLINE_NORMAL packArraysChunksOf #-}
178+packArraysChunksOf :: (MonadIO m, Storable a)
179+ => Int -> D.Stream m (Array a) -> D.Stream m (Array a)
180+packArraysChunksOf n str =
181+ D.map A.unsafeFreeze $ AS.packArraysChunksOf n $ D.map A.unsafeThaw str
182+
183+-- XXX instead of writing two different versions of this operation, we should
184+-- write it as a pipe.
185+{-# INLINE_NORMAL lpackArraysChunksOf #-}
186+lpackArraysChunksOf :: (MonadIO m, Storable a)
187+ => Int -> Fold m (Array a) () -> Fold m (Array a) ()
188+lpackArraysChunksOf n fld =
189+ FL.map A.unsafeThaw $ AS.lpackArraysChunksOf n (FL.map A.unsafeFreeze fld)
190+
191+#if !defined(mingw32_HOST_OS)
192+
193+-- | @groupIOVecsOf maxBytes maxEntries@ groups arrays in the incoming stream
194+-- to create a stream of 'IOVec' arrays with a maximum of @maxBytes@ bytes in
195+-- each array and a maximum of @maxEntries@ entries in each array.
196+--
197+-- @since 0.7.0
198+{-# INLINE_NORMAL groupIOVecsOf #-}
199+groupIOVecsOf :: MonadIO m
200+ => Int -> Int -> D.Stream m (Array a) -> D.Stream m (Array IOVec)
201+groupIOVecsOf n maxIOVLen str =
202+ D.map A.unsafeFreeze
203+ $ AS.groupIOVecsOf n maxIOVLen
204+ $ D.map A.unsafeThaw str
205+#endif
206+
207+-- | Coalesce adjacent arrays in incoming stream to form bigger arrays of a
208+-- maximum specified size in bytes.
209+--
210+-- @since 0.7.0
211+{-# INLINE compact #-}
212+compact :: (MonadIO m, Storable a)
213+ => Int -> SerialT m (Array a) -> SerialT m (Array a)
214+compact n xs = D.fromStreamD $ packArraysChunksOf n (D.toStreamD xs)
215+
216+-------------------------------------------------------------------------------
217+-- Split
218+-------------------------------------------------------------------------------
219+
220+data SplitState s arr
221+ = Initial s
222+ | Buffering s arr
223+ | Splitting s arr
224+ | Yielding arr (SplitState s arr)
225+ | Finishing
226+
227+-- | Split a stream of arrays on a given separator byte, dropping the separator
228+-- and coalescing all the arrays between two separators into a single array.
229+--
230+-- @since 0.7.0
231+{-# INLINE_NORMAL _splitOn #-}
232+_splitOn
233+ :: MonadIO m
234+ => Word8
235+ -> D.Stream m (Array Word8)
236+ -> D.Stream m (Array Word8)
237+_splitOn byte (D.Stream step state) = D.Stream step' (Initial state)
238+
239+ where
240+
241+ {-# INLINE_LATE step' #-}
242+ step' gst (Initial st) = do
243+ r <- step gst st
244+ case r of
245+ D.Yield arr s -> do
246+ (arr1, marr2) <- A.breakOn byte arr
247+ return $ case marr2 of
248+ Nothing -> D.Skip (Buffering s arr1)
249+ Just arr2 -> D.Skip (Yielding arr1 (Splitting s arr2))
250+ D.Skip s -> return $ D.Skip (Initial s)
251+ D.Stop -> return D.Stop
252+
253+ step' gst (Buffering st buf) = do
254+ r <- step gst st
255+ case r of
256+ D.Yield arr s -> do
257+ (arr1, marr2) <- A.breakOn byte arr
258+ buf' <- A.spliceTwo buf arr1
259+ return $ case marr2 of
260+ Nothing -> D.Skip (Buffering s buf')
261+ Just x -> D.Skip (Yielding buf' (Splitting s x))
262+ D.Skip s -> return $ D.Skip (Buffering s buf)
263+ D.Stop -> return $
264+ if A.byteLength buf == 0
265+ then D.Stop
266+ else D.Skip (Yielding buf Finishing)
267+
268+ step' _ (Splitting st buf) = do
269+ (arr1, marr2) <- A.breakOn byte buf
270+ return $ case marr2 of
271+ Nothing -> D.Skip $ Buffering st arr1
272+ Just arr2 -> D.Skip $ Yielding arr1 (Splitting st arr2)
273+
274+ step' _ (Yielding arr next) = return $ D.Yield arr next
275+ step' _ Finishing = return D.Stop
276+
105277-- | Split a stream of arrays on a given separator byte, dropping the separator
106278-- and coalescing all the arrays between two separators into a single array.
107279--
@@ -125,28 +297,9 @@ splitOnSuffix
125297splitOnSuffix byte s =
126298 D.fromStreamD $ D.splitInnerBySuffix (A.breakOn byte) A.spliceTwo $ D.toStreamD s
127299
128−-- | Coalesce adjacent arrays in incoming stream to form bigger arrays of a
129−-- maximum specified size in bytes.
130−--
131−-- @since 0.7.0
132−{-# INLINE compact #-}
133−compact :: (MonadIO m, Storable a)
134− => Int -> SerialT m (Array a) -> SerialT m (Array a)
135−compact n xs = D.fromStreamD $ A.packArraysChunksOf n (D.toStreamD xs)
136−
137−-- | @arraysOf n stream@ groups the elements in the input stream into arrays of
138−-- @n@ elements each.
139−--
140−-- Same as the following but more efficient:
141−--
142−-- > arraysOf n = S.chunksOf n (A.writeN n)
143−--
144−-- @since 0.7.0
145−{-# INLINE arraysOf #-}
146−arraysOf :: (IsStream t, MonadIO m, Storable a)
147− => Int -> t m a -> t m (Array a)
148−arraysOf n str =
149− D.fromStreamD $ A.fromStreamDArraysOf n (D.toStreamD str)
300+-------------------------------------------------------------------------------
301+-- Elimination
302+-------------------------------------------------------------------------------
150303
151304-- XXX Both of these implementations of splicing seem to perform equally well.
152305-- We need to perform benchmarks over a range of sizes though.
src/Streamly/Internal/Network/Socket.hs+2−1
@@ -89,7 +89,8 @@ import qualified Network.Socket as Net
8989
9090import Streamly.Prelude (MonadAsync)
9191import Streamly.Internal.Data.Unfold.Types (Unfold(..))
92−import Streamly.Internal.Data.Array.Storable.Foreign.Types (Array(..), lpackArraysChunksOf)
92+import Streamly.Internal.Memory.ArrayStream (lpackArraysChunksOf)
93+import Streamly.Internal.Data.Array.Storable.Foreign.Types (Array(..))
9394import Streamly.Internal.Data.Array.Storable.Foreign.Mut.Types (mutableArray)
9495import Streamly.Internal.Data.Stream.Serial (SerialT)
9596import Streamly.Internal.Data.Stream.StreamK.Type (IsStream, mkStream)
streamly.cabal+2
@@ -430,6 +430,7 @@ library
430430 , Streamly.Internal.Data.SVar
431431 , Streamly.Internal.Data.Stream.StreamK.Type
432432 , Streamly.Internal.Data.Fold.Types
433+ , Streamly.Internal.Data.Stream.StreamD.Step
433434 , Streamly.Internal.Data.Stream.StreamD.Type
434435 , Streamly.Internal.Data.Stream.StreamDK.Type
435436 , Streamly.Internal.Data.Unfold.Types
@@ -501,6 +502,7 @@ library
501502 , Streamly.Internal.Data.Array.Prim
502503 , Streamly.Internal.Data.Array.Prim.Pinned
503504 , Streamly.Internal.Data.SmallArray
505+ , Streamly.Internal.Data.Array.Stream.Mut.Foreign
504506 , Streamly.Internal.Memory.ArrayStream
505507
506508 -- streamly-unicode