on master help sign in
Add toArray fold to fold a stream into a single array
commitd0bba7667bf508cd397eb472970511a22873927c authorHarendra Kumar <harendra.kumar@gmail.com> dateSun 2 Jun 2019, 05:36 (7 years ago) parent3ce574a
3 files changed+135−32
examples/FileSinkServer.hs+3−3 src/Streamly/Mem/Array.hs+101−14 src/Streamly/Mem/Array/Types.hs+31−15
examples/FileSinkServer.hs+3−3
@@ -22,12 +22,12 @@ main = do
2222 File.append file
2323 $ encodeChar8Unchecked
2424 $ S.concatMap A.read
25− $ S.concatMapBy parallel recv
25+ $ S.concatMapBy parallel (flip NS.withSocketS recv)
2626 $ NS.connectionsOnAllAddrs 8090
2727
2828 where
2929
3030 recv =
31− FL.splitSuffixBy' (== '\n') (A.toArrayN 1024)
31+ FL.splitSuffixBy' (== '\n') A.toArray
3232 . decodeChar8
33− . NS.fromSocket
33+ . NS.read
src/Streamly/Mem/Array.hs+101−14
@@ -99,7 +99,7 @@ module Streamly.Mem.Array
9999 -- Folds
100100 , toArrayN
101101 -- , toArrays
102− -- , toArray
102+ , toArray
103103
104104 -- Streams
105105 , arraysOf
@@ -132,8 +132,9 @@ module Streamly.Mem.Array
132132where
133133
134134import Control.Monad.IO.Class (MonadIO(..))
135−import Data.Functor.Identity
135+import Data.Functor.Identity (Identity)
136136import Foreign.ForeignPtr (withForeignPtr)
137+import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
137138import Foreign.Ptr (minusPtr, plusPtr, castPtr)
138139import Foreign.Storable (Storable(..))
139140import Prelude hiding (length, null, last, map, (!!), read)
@@ -141,11 +142,13 @@ import Prelude hiding (length, null, last, map, (!!), read)
141142import Streamly.Mem.Array.Types hiding (flattenArrays, newArray)
142143import Streamly.Streams.Serial (SerialT)
143144import Streamly.Streams.StreamK.Type (IsStream)
144−import Streamly.Fold (Fold, lchunksOf, toStream)
145+import Streamly.Fold.Types (Fold(..))
145146
147+import qualified Streamly.Fold as FL
146148import qualified Streamly.Mem.Array.Types as A
147149import qualified Streamly.Prelude as S
148150import qualified Streamly.Streams.StreamD as D
151+import qualified Streamly.Streams.Prelude as P
149152
150153-------------------------------------------------------------------------------
151154-- Construction
@@ -353,11 +356,63 @@ writeSliceRev arr i len s = undefined
353356-- Streams of Arrays
354357-------------------------------------------------------------------------------
355358
359+-- exponentially increasing sizes of the chunks upto the max limit.
360+-- XXX this will be easier to implement with parsers/terminating folds
361+-- With this we should be able to reduce the number of chunks/allocations.
362+-- The reallocation/copy based toArray can also be implemented using this.
363+--
364+{-
365+{-# INLINE toArraysInRange #-}
366+toArraysInRange :: (IsStream t, MonadIO m, Storable a)
367+ => Int -> Int -> Fold m (Array a) b -> Fold m a b
368+toArraysInRange low high (Fold step initial extract) =
369+-}
370+
356371-- | Fold the input to a pure buffered stream (List) of arrays.
357−{-# INLINE _toArrays #-}
358−_toArrays :: (MonadIO m, Storable a)
372+{-# INLINE _toArraysOf #-}
373+_toArraysOf :: (MonadIO m, Storable a)
359374 => Int -> Fold m a (SerialT Identity (Array a))
360−_toArrays n = lchunksOf n (toArrayN n) toStream
375+_toArraysOf n = FL.lchunksOf n (toArrayN n) FL.toStream
376+
377+-- XXX The realloc based implementation needs to make one extra copy if we use
378+-- shrinkToFit. On the other hand, the stream of arrays implementation may
379+-- buffer the array chunk pointers in memory but it does not have to shrink as
380+-- we know the exact size in the end. However, memory copying does not seems to
381+-- be as expensive as the allocations. Therefore, we need to reduce the number
382+-- of allocations instead. Also, the size of allocations matters, right sizing
383+-- an allocation even at the cost of copying sems to help. Should be measured
384+-- on a big stream with heavy calls to toArray to see the effect.
385+--
386+-- XXX check if GHC's memory allocator is efficient enough. We can try the C
387+-- malloc to compare against.
388+
389+{-# INLINE toArrayMinChunk #-}
390+toArrayMinChunk :: forall m a. (MonadIO m, Storable a)
391+ => Int -> Fold m a (Array a)
392+-- toArrayMinChunk n = FL.mapM spliceArrays $ toArraysOf n
393+toArrayMinChunk chunkSize = Fold step initial extract
394+
395+ where
396+
397+ insertElem (Array start end bound) x = do
398+ liftIO $ poke end x
399+ return $ Array start (end `plusPtr` sizeOf (undefined :: a)) bound
400+
401+ initial = liftIO $ A.newArray (mkChunkSize chunkSize)
402+ step arr@(Array _ end bound) x | end == bound = do
403+ arr1 <- liftIO $ reallocDouble 1 arr
404+ insertElem arr1 x
405+ step arr x = insertElem arr x
406+ extract = liftIO . shrinkToFit
407+
408+-- | Fold the whole input to a single array.
409+--
410+-- /Caution! Do not use this on infinite streams./
411+--
412+-- @since 0.7.0
413+{-# INLINE toArray #-}
414+toArray :: forall m a. (MonadIO m, Storable a) => Fold m a (Array a)
415+toArray = toArrayMinChunk 1024
361416
362417-- | Convert a stream of arrays into a stream of their elements.
363418--
@@ -389,14 +444,14 @@ arraysOf :: (IsStream t, MonadIO m, Storable a)
389444arraysOf n str =
390445 D.fromStreamD $ fromStreamDArraysOf n (D.toStreamD str)
391446
392−-- | Given a stream of arrays, splice them all together to generate a single
393−-- array. The stream must be /finite/.
394−--
395−-- @since 0.7.0
396−{-# INLINABLE spliceArrays #-}
397−spliceArrays :: (MonadIO m, Storable a) => SerialT m (Array a) -> m (Array a)
398−spliceArrays s = do
399− buffered <- S.foldr S.cons S.nil s
447+-- XXX Both of these implementations of splicing seem to perform equally well.
448+-- We need to perform benchmarks over a range of sizes though.
449+
450+{-# INLINE _spliceArraysRealloced #-}
451+_spliceArraysRealloced :: (MonadIO m, Storable a)
452+ => SerialT m (Array a) -> m (Array a)
453+_spliceArraysRealloced s = do
454+ buffered <- P.foldr S.cons S.nil s
400455 len <- S.sum (S.map length buffered)
401456
402457 arr <- liftIO $ A.newArray len
@@ -411,6 +466,38 @@ spliceArrays s = do
411466 memcpy (castPtr dst) (castPtr src) len
412467 return $ dst `plusPtr` len
413468
469+{-# INLINE spliceArraysBuffered #-}
470+spliceArraysBuffered :: (MonadIO m, Storable a)
471+ => SerialT m (Array a) -> m (Array a)
472+spliceArraysBuffered s = do
473+ idst <- liftIO $ A.newArray (mkChunkSizeKB 4)
474+ arr <- S.foldlM' appendArr idst s
475+ liftIO $ shrinkToFit arr
476+
477+ where
478+
479+ appendArr dst@(Array _ end bound) src = liftIO $ do
480+ let srcLen = aEnd src `minusPtr` unsafeForeignPtrToPtr (aStart src)
481+ dst1 <-
482+ if end `plusPtr` srcLen >= bound
483+ then reallocDouble srcLen dst
484+ else return dst
485+
486+ withForeignPtr (aStart dst1) $ \_ -> do
487+ withForeignPtr (aStart src) $ \psrc -> do
488+ let pdst = aEnd dst1
489+ memcpy (castPtr pdst) (castPtr psrc) srcLen
490+ return $ dst1 { aEnd = pdst `plusPtr` srcLen }
491+
492+-- | Given a stream of arrays, splice them all together to generate a single
493+-- array. The stream must be /finite/.
494+--
495+-- @since 0.7.0
496+{-# INLINE spliceArrays #-}
497+spliceArrays :: (MonadIO m, Storable a) => SerialT m (Array a) -> m (Array a)
498+spliceArrays = spliceArraysBuffered
499+-- spliceArrays = _spliceArraysRealloced
500+
414501-- | Create an 'Array' from a stream. This is useful when we want to create a
415502-- single array from a stream of unknown size. 'writeN' is at least twice
416503-- as efficient when the size is already known.
src/Streamly/Mem/Array/Types.hs+31−15
@@ -54,7 +54,9 @@ module Streamly.Mem.Array.Types
5454
5555 -- * Utilities
5656 , defaultChunkSize
57+ , mkChunkSize
5758 , mkChunkSizeKB
59+ , reallocDouble
5860 )
5961where
6062
@@ -241,6 +243,26 @@ shrinkToFit arr@Array{..} = do
241243 }
242244 else return arr
243245
246+-- | Expand the free space in the array doubling the size with a minimum
247+-- exapnsion of the specified amount of bytes.
248+{-# NOINLINE reallocDouble #-}
249+reallocDouble :: forall a. Storable a => Int -> Array a -> IO (Array a)
250+reallocDouble minIncrease Array{..} = do
251+ assert (aEnd <= aBound) (return ())
252+ let oldStart = unsafeForeignPtrToPtr aStart
253+ let size = aEnd `minusPtr` oldStart
254+ newSize = max (size * 2) (size + minIncrease)
255+ newPtr <- mallocPlainForeignPtrAlignedBytes
256+ newSize (alignment (undefined :: a))
257+ withForeignPtr newPtr $ \pNew -> do
258+ memcpy (castPtr pNew) (castPtr oldStart) size
259+ touchForeignPtr aStart
260+ return $ Array
261+ { aStart = newPtr
262+ , aEnd = pNew `plusPtr` size
263+ , aBound = pNew `plusPtr` newSize
264+ }
265+
244266-- XXX when converting an array of Word8 from a literal string we can simply
245267-- refer to the literal string. Is it possible to write rules such that
246268-- fromList Word8 can be rewritten so that GHC does not first convert the
@@ -367,16 +389,7 @@ toArrayN n = Fold step initial extract
367389 step (Array start end bound) x = do
368390 liftIO $ poke end x
369391 return $ Array start (end `plusPtr` sizeOf (undefined :: a)) bound
370− extract = return
371−
372−{-
373−-- This can be implemented by combining the List of arrays from toArrays into a
374−-- single array.
375−-- | Fold the whole input to a single array.
376−{-# INLINE toArray #-}
377−toArray :: forall m a. (Monad m, Storable a) => Fold m a (Array a)
378−toArray = Fold step begin done
379−-}
392+ extract = liftIO . shrinkToFit
380393
381394{-# INLINE_NORMAL fromStreamDN #-}
382395fromStreamDN :: forall m a. (MonadIO m, Storable a)
@@ -638,13 +651,16 @@ read = D.fromStreamD . toStreamD
638651allocOverhead :: Int
639652allocOverhead = 2 * sizeOf (undefined :: Int)
640653
654+mkChunkSize :: Int -> Int
655+mkChunkSize n = n - allocOverhead
656+
657+mkChunkSizeKB :: Int -> Int
658+mkChunkSizeKB n = mkChunkSize (n * k)
659+ where k = 1024
660+
641661-- | Default maximum buffer size in bytes, for reading from and writing to IO
642662-- devices, the value is 32KB minus GHC allocation overhead, which is a few
643663-- bytes, so that the actual allocation is 32KB.
644664defaultChunkSize :: Int
645−defaultChunkSize = 32 * k - allocOverhead
646− where k = 1024
665+defaultChunkSize = mkChunkSizeKB 32
647666
648−mkChunkSizeKB :: Int -> Int
649−mkChunkSizeKB n = n * k - allocOverhead
650− where k = 1024