on master help sign in
update documentation and rename toArray => write

rename toArray/toArrayN to writeF/writeNF to keep the API name consistent with their regular counterparts. Now the only difference in fold APIs is a suffix F.

commite621237b79aea5fc72cf8aef7813a6bb7c874fb8 authorHarendra Kumar <harendra.kumar@gmail.com> dateMon 5 Aug 2019, 07:17 (7 years ago) parentd462530
7 files changed+139−161
benchmark/FileIO.hs+2−2 benchmark/Linear.hs+1−1 examples/FileSinkServer.hs+1−1 src/Streamly/Mem/Array.hs+95−140 src/Streamly/Mem/Array/Stream.hs+29−6 src/Streamly/Mem/Array/Types.hs+8−8 src/Streamly/String.hs+3−3
benchmark/FileIO.hs+2−2
@@ -168,9 +168,9 @@ main = do
168168 $ FH.read inh
169169 ]
170170 , bgroup "grouping"
171− [ mkBench "chunksOf 1 (toArray)" href $ do
171+ [ mkBench "chunksOf 1 (A.writeNF)" href $ do
172172 Handles inh _ <- readIORef href
173− S.length $ S.chunksOf fileSize (A.toArrayN fileSize)
173+ S.length $ S.chunksOf fileSize (A.writeNF fileSize)
174174 (FH.read inh)
175175
176176 , mkBench "chunksOf 1" href $ do
benchmark/Linear.hs+1−1
@@ -223,7 +223,7 @@ main =
223223 -- , benchIOSink "toListRev" (S.runFold FL.toListRev)
224224 -- , benchIOSink "toStream" (S.runFold FL.toStream)
225225 -- , benchIOSink "toStreamRev" (S.runFold FL.toStreamRev)
226− , benchIOSink "toArrayN" (S.runFold (A.toArrayN Ops.value))
226+ , benchIOSink "writeNF" (S.runFold (A.writeNF Ops.value))
227227
228228 , benchIOSink "index" (S.runFold (FL.index Ops.maxValue))
229229 , benchIOSink "head" (S.runFold FL.head)
examples/FileSinkServer.hs+1−1
@@ -30,6 +30,6 @@ main = do
3030 where
3131
3232 recv =
33− S.splitBySuffix (== '\n') A.toArray
33+ S.splitBySuffix (== '\n') A.writeF
3434 . decodeChar8
3535 . NS.read
src/Streamly/Mem/Array.hs+95−140
@@ -16,57 +16,40 @@
1616-- Stability : experimental
1717-- Portability : GHC
1818--
19−-- Arrays are the computing duals of streams. Streams are good at sequential
20−-- access and immutable transformations of in-transit data whereas arrays are
21−-- good at random access and in-place transformations of buffered data. Unlike
22−-- streams which are potentially infinite, arrays are necessarily /finite/.
23−-- Arrays can be used as an efficient interface between streams and external
24−-- storage systems like memory, files and network. Streams and arrays complete
25−-- each other to provide a general purpose computing system. The design of
26−-- streamly as a general purpose computing framework is centered around these
27−-- two fundamental aspects of computing and storage.
19+-- This module provides immutable arrays suitable for storage and random
20+-- access. Once created an array cannot be mutated without copying. Arrays in
21+-- this module are chunks of memory that hold a sequence of 'Storable' values
22+-- of a given type. They are designed to store serializable data, they cannot
23+-- store non-serializable data like functions, this is reflected by the
24+-- 'Storable' constraint. For efficient buffering of data, arrays use pinned
25+-- memory and therefore can hold arbitrary number of elements with a single
26+-- movable pointer visible to GC, therefore, adding no pressure to GC.
27+-- Moreover, pinned memory allows communication with foreign consumers and
28+-- producers (e.g. file or network IO) without copying the data.
29+--
30+-- By design, there are no array transformation operations provided in this
31+-- module, only conversion to and from stream is provided. Arrays can be
32+-- operated upon /efficiently/ by converting them into a stream, applying the
33+-- desired stream transformations from "Streamly.Prelude" and then converting
34+-- it back to an array.
2835--
29−-- Even though the implementation allows for in-place mutation, the current
30−-- implementation of arrays is immutable. Arrays are of fixed size. The size of
31−-- an array is fixed at creation and cannot be changed later without copying.
32−-- Arrays are chunks of memory that hold a sequence of 'Storable' values of a
33−-- given type. They are designed to store serializable data, they cannot store
34−-- non-serializable data like functions, this is reflected by the 'Storable'
35−-- constraint. For efficient buffering of data, arrays use pinned memory and
36−-- therefore can hold arbitrary number of elements with a single movable
37−-- pointer visible to GC, therefore, adding no pressure to GC. Moreover,
38−-- pinned memory allows communication with foreign consumers and producers
39−-- (e.g. file or network IO) without copying the data.
36+-- This module is designed to be imported qualified:
37+--
38+-- > import qualified Streamly.Array as A
39+
40+-- To summarize:
41+--
42+-- Arrays are finite and fixed in size
43+-- provide /O(1)/ access to elements
44+-- store only data and not functions
45+-- provide efficient IO interfacing
4046--
41−-- By design, there are no transformation operations provided in this module,
42−-- only stream conversion and IO routines are provided. An array is purely a
43−-- data store, therefore, it is not a 'Functor'. Arrays can be operated upon
44−-- /efficiently/ by converting them into a stream, applying the desired
45−-- transformations on the stream and then converting it back to an array.
4647-- 'Foldable' instance is not provided because the implementation would be much
4748-- less efficient compared to folding via streams. 'Semigroup' and 'Monoid'
4849-- instances are deliberately not provided to avoid misuse; concatenating
4950-- arrays using binary operations can be highly inefficient. Instead, use
50−-- 'spliceArrays' to concatenate N arrays at once.
51−--
52−-- To summarize:
53−--
54−-- * Arrays are finite and fixed in size
55−-- * provide /O(1)/ access to elements
56−-- * store only data and not functions
57−-- * provide efficient IO interfacing
51+-- 'Streamly.Mem.Array.Stream.toArray' to concatenate N arrays at once.
5852--
59−-- 'ByteString' data type from the 'bytestring' package and the 'Text' data
60−-- type from the 'text' package are just special cases of arrays. 'ByteString'
61−-- is equivalent to @Array Word8@ and 'Text' is equivalent to a @utf16@ encoded
62−-- @Array Word8@. All the 'bytestring' and 'text' operations can be performed
63−-- on arrays with equivalent or better performance by converting them to and
64−-- from streams.
65−--
66−-- This module is designed to be imported qualified:
67−--
68−-- > import qualified Streamly.Array as A
69−
7053-- Each array is one pointer visible to the GC. Too many small arrays (e.g.
7154-- single byte) are only as good as holding those elements in a Haskell list.
7255-- However, small arrays can be compacted into large ones to reduce the
@@ -100,9 +83,8 @@ module Streamly.Mem.Array
10083 , write
10184
10285 -- Stream Folds
103− , A.toArrayN
104− -- , toArrays
105− , toArray
86+ , A.writeNF
87+ , writeF
10688
10789 -- * Elimination
10890 -- 'GHC.Exts.toList' from "GHC.Exts" can be used to convert an array to a
@@ -128,12 +110,14 @@ module Streamly.Mem.Array
128110 , writeSliceRev
129111 -}
130112
113+ {-
131114 -- * Immutable Transformations
132115 , runTransform
133116
134117 -- * Folding Arrays
135118 -- , runStreamFold
136119 , runFold
120+ -}
137121 )
138122where
139123
@@ -179,6 +163,64 @@ writeN n m = do
179163 if n < 0 then error "writeN: negative write count specified" else return ()
180164 A.fromStreamDN n $ D.toStreamD m
181165
166+-- XXX The realloc based implementation needs to make one extra copy if we use
167+-- shrinkToFit. On the other hand, the stream of arrays implementation may
168+-- buffer the array chunk pointers in memory but it does not have to shrink as
169+-- we know the exact size in the end. However, memory copying does not seems to
170+-- be as expensive as the allocations. Therefore, we need to reduce the number
171+-- of allocations instead. Also, the size of allocations matters, right sizing
172+-- an allocation even at the cost of copying sems to help. Should be measured
173+-- on a big stream with heavy calls to toArray to see the effect.
174+--
175+-- XXX check if GHC's memory allocator is efficient enough. We can try the C
176+-- malloc to compare against.
177+
178+{-# INLINE_NORMAL toArrayMinChunk #-}
179+toArrayMinChunk :: forall m a. (MonadIO m, Storable a)
180+ => Int -> Fold m a (Array a)
181+-- toArrayMinChunk n = FL.mapM spliceArrays $ toArraysOf n
182+toArrayMinChunk elemCount = Fold step initial extract
183+
184+ where
185+
186+ insertElem (Array start end bound) x = do
187+ liftIO $ poke end x
188+ return $ Array start (end `plusPtr` sizeOf (undefined :: a)) bound
189+
190+ initial = do
191+ when (elemCount < 0) $ error "toArrayMinChunk: elemCount is negative"
192+ liftIO $ A.newArray elemCount
193+ step arr@(Array start end bound) x | end == bound = do
194+ let p = unsafeForeignPtrToPtr start
195+ oldSize = end `minusPtr` p
196+ newSize = max (oldSize * 2) 1
197+ arr1 <- liftIO $ A.realloc newSize arr
198+ insertElem arr1 x
199+ step arr x = insertElem arr x
200+ extract = liftIO . A.shrinkToFit
201+
202+-- | Fold the whole input to a single array.
203+--
204+-- /Caution! Do not use this on infinite streams./
205+--
206+-- @since 0.7.0
207+{-# INLINE writeF #-}
208+writeF :: forall m a. (MonadIO m, Storable a) => Fold m a (Array a)
209+writeF = toArrayMinChunk (A.bytesToCount (undefined :: a) (A.mkChunkSize 1024))
210+
211+-- | Create an 'Array' from a stream. This is useful when we want to create a
212+-- single array from a stream of unknown size. 'writeN' is at least twice
213+-- as efficient when the size is already known.
214+--
215+-- Note that if the input stream is too large memory allocation for the array
216+-- may fail. When the stream size is not known, `arraysOf` followed by
217+-- processing of indvidual arrays in the resulting stream should be preferred.
218+--
219+{-# INLINE write #-}
220+write :: (MonadIO m, Storable a) => SerialT m a -> m (Array a)
221+write = P.runFold writeF
222+-- write m = A.fromStreamD $ D.toStreamD m
223+
182224-------------------------------------------------------------------------------
183225-- Elimination
184226-------------------------------------------------------------------------------
@@ -373,93 +415,6 @@ writeSliceRev :: (IsStream t, Monad m, Storable a)
373415writeSliceRev arr i len s = undefined
374416-}
375417
376−-------------------------------------------------------------------------------
377−-- Streams of Arrays
378−-------------------------------------------------------------------------------
379−
380−-- exponentially increasing sizes of the chunks upto the max limit.
381−-- XXX this will be easier to implement with parsers/terminating folds
382−-- With this we should be able to reduce the number of chunks/allocations.
383−-- The reallocation/copy based toArray can also be implemented using this.
384−--
385−{-
386−{-# INLINE toArraysInRange #-}
387−toArraysInRange :: (IsStream t, MonadIO m, Storable a)
388− => Int -> Int -> Fold m (Array a) b -> Fold m a b
389−toArraysInRange low high (Fold step initial extract) =
390−-}
391−
392−{-
393−-- | Fold the input to a pure buffered stream (List) of arrays.
394−{-# INLINE _toArraysOf #-}
395−_toArraysOf :: (MonadIO m, Storable a)
396− => Int -> Fold m a (SerialT Identity (Array a))
397−_toArraysOf n = FL.lchunksOf n (A.toArrayN n) FL.toStream
398−-}
399−
400−-- XXX The realloc based implementation needs to make one extra copy if we use
401−-- shrinkToFit. On the other hand, the stream of arrays implementation may
402−-- buffer the array chunk pointers in memory but it does not have to shrink as
403−-- we know the exact size in the end. However, memory copying does not seems to
404−-- be as expensive as the allocations. Therefore, we need to reduce the number
405−-- of allocations instead. Also, the size of allocations matters, right sizing
406−-- an allocation even at the cost of copying sems to help. Should be measured
407−-- on a big stream with heavy calls to toArray to see the effect.
408−--
409−-- XXX check if GHC's memory allocator is efficient enough. We can try the C
410−-- malloc to compare against.
411−
412−{-# INLINE_NORMAL toArrayMinChunk #-}
413−toArrayMinChunk :: forall m a. (MonadIO m, Storable a)
414− => Int -> Fold m a (Array a)
415−-- toArrayMinChunk n = FL.mapM spliceArrays $ toArraysOf n
416−toArrayMinChunk elemCount = Fold step initial extract
417−
418− where
419−
420− insertElem (Array start end bound) x = do
421− liftIO $ poke end x
422− return $ Array start (end `plusPtr` sizeOf (undefined :: a)) bound
423−
424− initial = do
425− when (elemCount < 0) $ error "toArrayMinChunk: elemCount is negative"
426− liftIO $ A.newArray elemCount
427− step arr@(Array start end bound) x | end == bound = do
428− let p = unsafeForeignPtrToPtr start
429− oldSize = end `minusPtr` p
430− newSize = max (oldSize * 2) 1
431− arr1 <- liftIO $ A.realloc newSize arr
432− insertElem arr1 x
433− step arr x = insertElem arr x
434− extract = liftIO . A.shrinkToFit
435−
436−-- | Fold the whole input to a single array.
437−--
438−-- /Caution! Do not use this on infinite streams./
439−--
440−-- @since 0.7.0
441−{-# INLINE toArray #-}
442−toArray :: forall m a. (MonadIO m, Storable a) => Fold m a (Array a)
443−toArray =
444− toArrayMinChunk (A.bytesToCount (undefined :: a) (A.mkChunkSize 1024))
445−
446−-- | Create an 'Array' from a stream. This is useful when we want to create a
447−-- single array from a stream of unknown size. 'writeN' is at least twice
448−-- as efficient when the size is already known.
449−--
450−-- Note that if the input stream is too large memory allocation for the array
451−-- may fail. When the stream size is not known, `arraysOf` followed by
452−-- processing of indvidual arrays in the resulting stream should be preferred.
453−--
454−{-# INLINE write #-}
455−write :: (MonadIO m, Storable a) => SerialT m a -> m (Array a)
456−write = P.runFold toArray
457−-- write m = A.fromStreamD $ D.toStreamD m
458−
459−-- XXX efficiently compare two streams of arrays. Two streams can have chunks
460−-- of different sizes, we can handle that in the stream comparison abstraction.
461−-- This could be useful e.g. to fast compare whether two files differ.
462−
463418-------------------------------------------------------------------------------
464419-- Transform via stream operations
465420-------------------------------------------------------------------------------
@@ -483,17 +438,17 @@ runPipe f arr = P.runPipe (toArrayMinChunk (length arr)) $ f (A.read arr)
483438-- operation.
484439--
485440-- @since 0.7.0
486−{-# INLINE runTransform #-}
487−runTransform :: (MonadIO m, Storable a, Storable b)
441+{-# INLINE _runTransform #-}
442+_runTransform :: (MonadIO m, Storable a, Storable b)
488443 => (SerialT m a -> SerialT m b) -> Array a -> m (Array b)
489−runTransform f arr = P.runFold (toArrayMinChunk (length arr)) $ f (A.read arr)
444+_runTransform f arr = P.runFold (toArrayMinChunk (length arr)) $ f (A.read arr)
490445
491446-- | Fold an array using a 'Fold'.
492447--
493448-- @since 0.7.0
494−{-# INLINE runFold #-}
495−runFold :: forall m a b. (MonadIO m, Storable a) => Fold m a b -> Array a -> m b
496−runFold f arr = P.runFold f $ (A.read arr :: Serial.SerialT m a)
449+{-# INLINE _runFold #-}
450+_runFold :: forall m a b. (MonadIO m, Storable a) => Fold m a b -> Array a -> m b
451+_runFold f arr = P.runFold f $ (A.read arr :: Serial.SerialT m a)
497452
498453-- | Fold an array using a stream fold operation.
499454--
src/Streamly/Mem/Array/Stream.hs+29−6
@@ -54,6 +54,10 @@ import qualified Streamly.Prelude as S
5454import qualified Streamly.Streams.StreamD as D
5555import qualified Streamly.Streams.Prelude as P
5656
57+-- XXX efficiently compare two streams of arrays. Two streams can have chunks
58+-- of different sizes, we can handle that in the stream comparison abstraction.
59+-- This could be useful e.g. to fast compare whether two files differ.
60+
5761-- | Convert a stream of arrays into a stream of their elements.
5862--
5963-- Same as the following but more efficient:
@@ -100,21 +104,21 @@ splitOn
100104 -> t m (Array Word8)
101105splitOn byte s = D.fromStreamD $ A.splitOn byte $ D.toStreamD s
102106
103−-- | Coalesce adajcent arrays in incoming stream to form bigger arrays of a
104−-- maximum specified size.
107+-- | Coalesce adjacent arrays in incoming stream to form bigger arrays of a
108+-- maximum specified size in bytes.
105109--
106110-- @since 0.7.0
107111{-# INLINE compact #-}
108112compact :: (MonadIO m, Storable a)
109113 => Int -> SerialT m (Array a) -> SerialT m (Array a)
110−compact n xs =
111− D.fromStreamD $ A.packArraysChunksOf n (D.toStreamD xs)
114+compact n xs = D.fromStreamD $ A.packArraysChunksOf n (D.toStreamD xs)
112115
113−-- | Groups the elements in an input stream into arrays of given size.
116+-- | @arraysOf n stream@ groups the elements in the input stream into arrays of
117+-- @n@ elements each.
114118--
115119-- Same as the following but more efficient:
116120--
117−-- > arraysOf n = FL.groupsOf n (FL.toArrayN n)
121+-- > arraysOf n = FL.chunksOf n (A.writeNF n)
118122--
119123-- @since 0.7.0
120124{-# INLINE arraysOf #-}
@@ -171,3 +175,22 @@ toArray :: (MonadIO m, Storable a) => SerialT m (Array a) -> m (Array a)
171175toArray = spliceArraysRealloced
172176-- spliceArrays = _spliceArraysBuffered
173177
178+-- exponentially increasing sizes of the chunks upto the max limit.
179+-- XXX this will be easier to implement with parsers/terminating folds
180+-- With this we should be able to reduce the number of chunks/allocations.
181+-- The reallocation/copy based toArray can also be implemented using this.
182+--
183+{-
184+{-# INLINE toArraysInRange #-}
185+toArraysInRange :: (IsStream t, MonadIO m, Storable a)
186+ => Int -> Int -> Fold m (Array a) b -> Fold m a b
187+toArraysInRange low high (Fold step initial extract) =
188+-}
189+
190+{-
191+-- | Fold the input to a pure buffered stream (List) of arrays.
192+{-# INLINE _toArraysOf #-}
193+_toArraysOf :: (MonadIO m, Storable a)
194+ => Int -> Fold m a (SerialT Identity (Array a))
195+_toArraysOf n = FL.lchunksOf n (A.writeNF n) FL.toStream
196+-}
src/Streamly/Mem/Array/Types.hs+8−8
@@ -59,7 +59,7 @@ module Streamly.Mem.Array.Types
5959 , toStreamK
6060 , toStreamKRev
6161 , toList
62− , toArrayN
62+ , writeNF
6363 , read
6464
6565 -- * Utilities
@@ -472,18 +472,18 @@ foldr f z arr = runIdentity $ D.foldr f z $ toStreamD arr
472472-- Instances
473473-------------------------------------------------------------------------------
474474
475−-- | @toArrayN n@ folds a maximum of @n@ elements from the input stream to an
475+-- | @writeNF n@ folds a maximum of @n@ elements from the input stream to an
476476-- 'Array'.
477477--
478478-- @since 0.7.0
479−{-# INLINE_NORMAL toArrayN #-}
480−toArrayN :: forall m a. (MonadIO m, Storable a) => Int -> Fold m a (Array a)
481−toArrayN n = Fold step initial extract
479+{-# INLINE_NORMAL writeNF #-}
480+writeNF :: forall m a. (MonadIO m, Storable a) => Int -> Fold m a (Array a)
481+writeNF n = Fold step initial extract
482482
483483 where
484484
485485 initial = do
486− if n < 0 then error "toArrayN: negative count specified" else return ()
486+ if n < 0 then error "writeNF: negative count specified" else return ()
487487 liftIO $ newArray n
488488 step arr@(Array _ end bound) _ | end == bound = return arr
489489 step (Array start end bound) x = do
@@ -516,7 +516,7 @@ data GroupState s start end bound
516516{-# INLINE_NORMAL fromStreamDArraysOf #-}
517517fromStreamDArraysOf :: forall m a. (MonadIO m, Storable a)
518518 => Int -> D.Stream m a -> D.Stream m (Array a)
519−-- fromStreamDArraysOf n str = D.groupsOf n (toArrayN n) str
519+-- fromStreamDArraysOf n str = D.groupsOf n (writeNF n) str
520520fromStreamDArraysOf n (D.Stream step state) =
521521 D.Stream step' (GroupStart state)
522522
@@ -886,7 +886,7 @@ data SpliceState s arr
886886 | SpliceFinish
887887
888888-- XXX can use general grouping combinators to achieve this?
889−-- | Coalesce adajcent arrays in incoming stream to form bigger arrays of a
889+-- | Coalesce adjacent arrays in incoming stream to form bigger arrays of a
890890-- maximum specified size.
891891--
892892-- @since 0.7.0
src/Streamly/String.hs+3−3
@@ -70,7 +70,7 @@ import GHC.Base (unsafeChr)
7070import Streamly (IsStream, MonadAsync)
7171import Prelude hiding (String, lines, words, unlines, unwords)
7272import Streamly.Fold (Fold)
73−import Streamly.Mem.Array (Array, toArray)
73+import Streamly.Mem.Array (Array)
7474
7575import qualified Streamly.Prelude as S
7676import qualified Streamly.Mem.Array.Types as A (unlines)
@@ -204,7 +204,7 @@ isSpace c
204204-- 'foldLines' instead.
205205{-# INLINE lines #-}
206206lines :: (MonadIO m, IsStream t) => t m Char -> t m (Array Char)
207−lines = S.splitOnSuffix (== '\n') toArray
207+lines = S.splitOnSuffix (== '\n') A.writeF
208208
209209-- | Break a string up into a list of strings, which were delimited
210210-- by characters representing white space.
@@ -216,7 +216,7 @@ lines = S.splitOnSuffix (== '\n') toArray
216216-- 'foldWords' instead.
217217{-# INLINE words #-}
218218words :: (MonadIO m, IsStream t) => t m Char -> t m (Array Char)
219−words = S.wordsBy isSpace toArray
219+words = S.wordsBy isSpace A.writeF
220220
221221-- | Flattens the stream of @Array Char@, after appending a terminating
222222-- newline to each string.