on master help sign in
implement fromStreamDArraysOf more efficiently

The implementation of fromStreamDArraysOf is now 3x more efficient compared to the earlier implementation. This makes byte stream level operations almost as efficient as array level operations. Other than this the following changes are added in this commit: * Add insertAfterEach * Add writeArraysPackedUpto to Handle IO * Implement `wc -l` example more efficiently using arrays * Add benchmark for lines/unlines using arrays * Add tests for splitArraysOn * Rename some array/file/handle APIs * Error handling when the group size in grouping operations is 0 or negative

commitdbaacea1c6693ed2f8d4325fcd8bb0c38e67d42c authorHarendra Kumar <harendra.kumar@gmail.com> dateSat 15 Jun 2019, 13:19 (7 years ago) parent694eab4
12 files changed+169−33
benchmark/FileIO.hs+12−6 examples/FileIO.hs+3−3 examples/HandleIO.hs+9 src/Streamly/FileSystem/FD.hs+1−1 src/Streamly/FileSystem/Handle.hs+16−4 src/Streamly/Mem/Array.hs+9−8 src/Streamly/Mem/Array/Types.hs+58−7 src/Streamly/Prelude.hs+5 src/Streamly/Streams/StreamD.hs+23 src/Streamly/Streams/StreamD/Type.hs+8−1 streamly.cabal+2 test/String.hs+23−3
benchmark/FileIO.hs+12−6
@@ -135,14 +135,14 @@ main = do
135135 FH.write outh (FH.read inh)
136136 ]
137137 -- This needs an ascii file, as decode just errors out.
138− , bgroup "decode/encode"
139− [ mkBench "encodeChar8/decodeChar8" href $ do
138+ , bgroup "decode-encode"
139+ [ mkBench "encodeChar8-decodeChar8" href $ do
140140 Handles inh outh <- readIORef href
141141 FH.write outh
142142 $ SS.encodeChar8
143143 $ SS.decodeChar8
144144 $ FH.read inh
145− , mkBench "encodeUtf8/decodeUtf8" href $ do
145+ , mkBench "encodeUtf8-decodeUtf8" href $ do
146146 Handles inh outh <- readIORef href
147147 FH.write outh
148148 $ SS.encodeUtf8
@@ -165,8 +165,8 @@ main = do
165165 Handles inh _ <- readIORef href
166166 S.length $ FL.chunksOf 1000 FL.drain (FH.read inh)
167167 ]
168− , bgroup "group/ungroup"
169− [ mkBench "lines/unlines" href $ do
168+ , bgroup "group-ungroup"
169+ [ mkBench "lines-unlines" href $ do
170170 Handles inh outh <- readIORef href
171171 FH.write outh
172172 $ SS.encodeChar8
@@ -174,7 +174,13 @@ main = do
174174 $ SS.lines
175175 $ SS.decodeChar8
176176 $ FH.read inh
177− , mkBench "words/unwords" href $ do
177+ , mkBench "lines-unlines-arrays" href $ do
178+ Handles inh outh <- readIORef href
179+ FH.writeArraysPackedUpto (1024*1024) outh
180+ $ S.insertAfterEach (return $ A.fromList [10])
181+ $ A.splitArraysOn 10
182+ $ FH.readArraysOfUpto (1024*1024) inh
183+ , mkBench "words-unwords" href $ do
178184 Handles inh outh <- readIORef href
179185 FH.write outh
180186 $ SS.encodeChar8
examples/FileIO.hs+3−3
@@ -7,13 +7,13 @@ import Data.Char (ord)
77import System.Environment (getArgs)
88
99cat :: FilePath -> IO ()
10−cat src = File.writeArrays "/dev/stdout" $ File.readArraysUpto (256*1024) src
10+cat src = File.writeArrays "/dev/stdout" $ File.readArraysOfUpto (256*1024) src
1111
1212cp :: FilePath -> FilePath -> IO ()
13−cp src dst = File.writeArrays dst $ File.readArraysUpto (256*1024) src
13+cp src dst = File.writeArrays dst $ File.readArraysOfUpto (256*1024) src
1414
1515append :: FilePath -> FilePath -> IO ()
16−append src dst = File.appendArrays dst $ File.readArraysUpto (256*1024) src
16+append src dst = File.appendArrays dst $ File.readArraysOfUpto (256*1024) src
1717
1818ord' :: Num a => Char -> a
1919ord' = (fromIntegral . ord)
examples/HandleIO.hs+9
@@ -5,6 +5,7 @@ import qualified Streamly.FileSystem.Handle as FH
55import qualified System.IO as FH
66-- import qualified Streamly.FileSystem.FD as FH
77import qualified Streamly.String as SS
8+import Data.Word (Word8)
89
910import Data.Char (ord)
1011import System.Environment (getArgs)
@@ -13,6 +14,7 @@ import System.IO (IOMode(..), hSeek, SeekMode(..))
1314cat :: FH.Handle -> IO ()
1415cat src = FH.writeArrays FH.stdout $ FH.readArraysOfUpto (256*1024) src
1516{-
17+-- byte stream version
1618cat src =
1719 FH.writeByChunksOf (1024*1024) FH.stdout
1820 $ FH.readByChunksUpto (16*1024) src
@@ -21,6 +23,7 @@ cat src =
2123cp :: FH.Handle -> FH.Handle -> IO ()
2224cp src dst = FH.writeArrays dst $ FH.readArraysOfUpto (256*1024) src
2325{-
26+-- byte stream version
2427cp src dst =
2528 FH.writeByChunksOf (1024*1024) dst
2629 $ FH.readByChunksUpto (16*1024) src
@@ -30,10 +33,16 @@ ord' :: Num a => Char -> a
3033ord' = (fromIntegral . ord)
3134
3235wcl :: FH.Handle -> IO ()
36+wcl src = print =<< (S.length
37+ $ A.splitArraysOn 10
38+ $ FH.readArrays src)
39+{-
40+-- Char stream version
3341wcl src = print =<< (S.length
3442 $ flip SS.foldLines FL.drain
3543 $ SS.decodeChar8
3644 $ FH.read src)
45+-}
3746
3847grepc :: String -> FH.Handle -> IO ()
3948grepc pat src = print . (subtract 1) =<< (S.length
src/Streamly/FileSystem/FD.hs+1−1
@@ -358,7 +358,7 @@ writev h m = S.mapM_ (liftIO . writeIOVec h) m
358358{-# INLINE writeArraysOfUpto #-}
359359writeArraysOfUpto :: (MonadIO m, Storable a)
360360 => Int -> Handle -> SerialT m (Array a) -> m ()
361−writeArraysOfUpto n h xs = writeArrays h $ A.coalesceChunksOf n xs
361+writeArraysOfUpto n h xs = writeArrays h $ A.packArraysChunksOf n xs
362362
363363-- | Write a stream of arrays to a handle after grouping them in 'IOVec' arrays
364364-- of up to a maximum total size. Writes are performed using gather IO via
src/Streamly/FileSystem/Handle.hs+16−4
@@ -73,6 +73,7 @@ module Streamly.FileSystem.Handle
7373 -- -- * Array Write
7474 , writeArray
7575 , writeArrays
76+ , writeArraysPackedUpto
7677
7778 -- -- * Random Access (Seek)
7879 -- -- | Unlike the streaming APIs listed above, these APIs apply to devices or
@@ -108,11 +109,11 @@ import Prelude hiding (read)
108109import Streamly.Mem.Array.Types (Array(..))
109110import Streamly.Streams.Serial (SerialT)
110111import Streamly.Streams.StreamK.Type (IsStream, mkStream)
112+import Streamly.Mem.Array.Types (defaultChunkSize, shrinkToFit)
111113-- import Streamly.Fold (Fold)
112114-- import Streamly.String (encodeUtf8, decodeUtf8, foldLines)
113115
114116import qualified Streamly.Mem.Array as A
115−import qualified Streamly.Mem.Array.Types as A hiding (flattenArrays)
116117import qualified Streamly.Prelude as S
117118import qualified Streamly.Streams.StreamD.Type as D
118119
@@ -150,7 +151,7 @@ readArrayUpto size h = do
150151 , aBound = p `plusPtr` size
151152 }
152153 -- XXX shrink only if the diff is significant
153− A.shrinkToFit v
154+ shrinkToFit v
154155
155156-------------------------------------------------------------------------------
156157-- Array IO (output)
@@ -211,7 +212,7 @@ readArraysOfUpto size h = D.fromStreamD (D.Stream step ())
211212-- @since 0.7.0
212213{-# INLINE readArrays #-}
213214readArrays :: (IsStream t, MonadIO m) => Handle -> t m (Array Word8)
214−readArrays = readArraysOfUpto A.defaultChunkSize
215+readArrays = readArraysOfUpto defaultChunkSize
215216
216217-------------------------------------------------------------------------------
217218-- Read File to Stream
@@ -252,6 +253,17 @@ read = A.flattenArrays . readArrays
252253writeArrays :: (MonadIO m, Storable a) => Handle -> SerialT m (Array a) -> m ()
253254writeArrays h m = S.mapM_ (liftIO . writeArray h) m
254255
256+-- | Write a stream of arrays to a handle after coalescing them in chunks of
257+-- specified size. The chunk size is only a maximum and the actual writes could
258+-- be smaller than that as we do not split the arrays to fit them to the
259+-- specified size.
260+--
261+-- @since 0.7.0
262+{-# INLINE writeArraysPackedUpto #-}
263+writeArraysPackedUpto :: (MonadIO m, Storable a)
264+ => Int -> Handle -> SerialT m (Array a) -> m ()
265+writeArraysPackedUpto n h xs = writeArrays h $ A.packArraysChunksOf n xs
266+
255267-- GHC buffer size dEFAULT_FD_BUFFER_SIZE=8192 bytes.
256268--
257269-- XXX test this
@@ -278,7 +290,7 @@ writeByChunksOf n h m = writeArrays h $ A.arraysOf n m
278290-- @since 0.7.0
279291{-# INLINE write #-}
280292write :: MonadIO m => Handle -> SerialT m Word8 -> m ()
281−write = writeByChunksOf A.defaultChunkSize
293+write = writeByChunksOf defaultChunkSize
282294
283295{-
284296{-# INLINE write #-}
src/Streamly/Mem/Array.hs+9−8
@@ -115,8 +115,8 @@ module Streamly.Mem.Array
115115 -- Streams of arrays
116116 , flattenArrays
117117 -- , flattenArraysRev
118− , coalesceArrays
119− , coalesceChunksOf
118+ , packArrays
119+ , packArraysChunksOf
120120 , unlinesArraysBy
121121 , splitArraysOn
122122
@@ -500,10 +500,11 @@ splitArraysOn byte s = D.fromStreamD $ A.splitOn byte $ D.toStreamD s
500500-- maximum specified size.
501501--
502502-- @since 0.7.0
503−{-# INLINE coalesceChunksOf #-}
504−coalesceChunksOf :: (MonadIO m, Storable a)
503+{-# INLINE packArraysChunksOf #-}
504+packArraysChunksOf :: (MonadIO m, Storable a)
505505 => Int -> SerialT m (Array a) -> SerialT m (Array a)
506−coalesceChunksOf n xs = D.fromStreamD $ A.coalesceChunksOf n (D.toStreamD xs)
506+packArraysChunksOf n xs =
507+ D.fromStreamD $ A.packArraysChunksOf n (D.toStreamD xs)
507508
508509-- |
509510-- > arraysOf n = FL.groupsOf n (FL.toArrayN n)
@@ -560,9 +561,9 @@ spliceArraysRealloced s = do
560561-- array. The stream must be /finite/.
561562--
562563-- @since 0.7.0
563−{-# INLINE coalesceArrays #-}
564−coalesceArrays :: (MonadIO m, Storable a) => SerialT m (Array a) -> m (Array a)
565−coalesceArrays = spliceArraysRealloced
564+{-# INLINE packArrays #-}
565+packArrays :: (MonadIO m, Storable a) => SerialT m (Array a) -> m (Array a)
566+packArrays = spliceArraysRealloced
566567-- spliceArrays = _spliceArraysBuffered
567568
568569-- | Create an 'Array' from a stream. This is useful when we want to create a
src/Streamly/Mem/Array/Types.hs+58−7
@@ -38,7 +38,7 @@ module Streamly.Mem.Array.Types
3838 , fromStreamDArraysOf
3939 , flattenArrays
4040 , flattenArraysRev
41− , coalesceChunksOf
41+ , packArraysChunksOf
4242 , groupIOVecsOf
4343 , splitOn
4444
@@ -485,12 +485,49 @@ fromStreamDN limit str = do
485485 liftIO $ poke ptr x
486486 return $ ptr `plusPtr` sizeOf (undefined :: a)
487487
488+data GroupState s start end bound
489+ = GroupStart s
490+ | GroupBuffer s start end bound
491+ | GroupYield start end bound (GroupState s start end bound)
492+ | GroupFinish
493+
488494-- | @fromStreamArraysOf n stream@ groups the input stream into a stream of
489495-- arrays of size n.
490−{-# INLINE fromStreamDArraysOf #-}
496+{-# INLINE_NORMAL fromStreamDArraysOf #-}
491497fromStreamDArraysOf :: (MonadIO m, Storable a)
492498 => Int -> D.Stream m a -> D.Stream m (Array a)
493−fromStreamDArraysOf n str = D.groupsOf n (toArrayN n) str
499+-- fromStreamDArraysOf n str = D.groupsOf n (toArrayN n) str
500+fromStreamDArraysOf n (D.Stream step state) =
501+ D.Stream step' (GroupStart state)
502+
503+ where
504+
505+ {-# INLINE_LATE step' #-}
506+ step' _ (GroupStart st) = do
507+ when (n <= 0) $
508+ -- XXX we can pass the module string from the higher level API
509+ error $ "Streamly.Mem.Array.Types.fromStreamDArraysOf: the size of "
510+ ++ "arrays [" ++ show n ++ "] must be a natural number"
511+ Array start end bound <- liftIO $ newArray n
512+ return $ D.Skip (GroupBuffer st start end bound)
513+
514+ step' gst (GroupBuffer st start end bound) = do
515+ r <- step (adaptState gst) st
516+ case r of
517+ D.Yield x s -> do
518+ liftIO $ poke end x
519+ let end' = end `plusPtr` 1
520+ return $
521+ if end' >= bound
522+ then D.Skip (GroupYield start end' bound (GroupStart s))
523+ else D.Skip (GroupBuffer s start end' bound)
524+ D.Skip s -> return $ D.Skip (GroupBuffer s start end bound)
525+ D.Stop -> return $ D.Skip (GroupYield start end bound GroupFinish)
526+
527+ step' _ (GroupYield start end bound next) =
528+ return $ D.Yield (Array start end bound) next
529+
530+ step' _ GroupFinish = return D.Stop
494531
495532-- XXX concatMap does not seem to have the best possible performance so we have
496533-- a custom way to concat arrays.
@@ -833,15 +870,20 @@ data SpliceState s arr
833870-- maximum specified size.
834871--
835872-- @since 0.7.0
836−{-# INLINE_NORMAL coalesceChunksOf #-}
837−coalesceChunksOf :: (MonadIO m, Storable a)
873+{-# INLINE_NORMAL packArraysChunksOf #-}
874+packArraysChunksOf :: (MonadIO m, Storable a)
838875 => Int -> D.Stream m (Array a) -> D.Stream m (Array a)
839−coalesceChunksOf n (D.Stream step state) = D.Stream step' (SpliceInitial state)
876+packArraysChunksOf n (D.Stream step state) =
877+ D.Stream step' (SpliceInitial state)
840878
841879 where
842880
843881 {-# INLINE_LATE step' #-}
844882 step' gst (SpliceInitial st) = do
883+ when (n <= 0) $
884+ -- XXX we can pass the module string from the higher level API
885+ error $ "Streamly.Mem.Array.Types.packArraysChunksOf: the size of "
886+ ++ "arrays [" ++ show n ++ "] must be a natural number"
845887 r <- step gst st
846888 case r of
847889 D.Yield arr s -> return $
@@ -858,7 +900,8 @@ coalesceChunksOf n (D.Stream step state) = D.Stream step' (SpliceInitial state)
858900 D.Yield arr s -> do
859901 let len = byteLength buf + byteLength arr
860902 if len > n
861− then return $ D.Skip (SpliceYielding buf (SpliceBuffering s arr))
903+ then return $
904+ D.Skip (SpliceYielding buf (SpliceBuffering s arr))
862905 else do
863906 buf' <- if byteCapacity buf < n
864907 then liftIO $ realloc n buf
@@ -893,6 +936,14 @@ groupIOVecsOf n maxIOVLen (D.Stream step state) =
893936
894937 {-# INLINE_LATE step' #-}
895938 step' gst (GatherInitial st) = do
939+ when (n <= 0) $
940+ -- XXX we can pass the module string from the higher level API
941+ error $ "Streamly.Mem.Array.Types.groupIOVecsOf: the size of "
942+ ++ "groups [" ++ show n ++ "] must be a natural number"
943+ when (maxIOVLen <= 0) $
944+ -- XXX we can pass the module string from the higher level API
945+ error $ "Streamly.Mem.Array.Types.groupIOVecsOf: the number of "
946+ ++ "IOVec entries [" ++ show n ++ "] must be a natural number"
896947 r <- step (adaptState gst) st
897948 case r of
898949 D.Yield arr s -> do
src/Streamly/Prelude.hs+5
@@ -370,6 +370,7 @@ module Streamly.Prelude
370370 , insertBy
371371 , intersperseM
372372 , intersperse
373+ , insertAfterEach
373374 -- , intersperseBySpan
374375 , intersperseByTime
375376
@@ -1949,6 +1950,10 @@ intersperseM m = fromStreamS . S.intersperseM m . toStreamS
19491950intersperse :: (IsStream t, MonadAsync m) => a -> t m a -> t m a
19501951intersperse a = fromStreamS . S.intersperse a . toStreamS
19511952
1953+{-# INLINE insertAfterEach #-}
1954+insertAfterEach :: (IsStream t, MonadAsync m) => m a -> t m a -> t m a
1955+insertAfterEach m = fromStreamS . S.insertAfterEach m . toStreamS
1956+
19521957{-
19531958-- | Intersperse a monadic action into the input stream after every @n@
19541959-- elements.
src/Streamly/Streams/StreamD.hs+23
@@ -205,6 +205,7 @@ module Streamly.Streams.StreamD
205205 -- * Inserting
206206 , intersperseM
207207 , intersperse
208+ , insertAfterEach
208209 , insertBy
209210
210211 -- * Deleting
@@ -2204,6 +2205,28 @@ intersperseM m (Stream step state) = Stream step' (FirstYield state)
22042205
22052206 step' _ (YieldAndCarry x st) = return $ Yield x (InterspersingYield st)
22062207
2208+data SuffixState s a
2209+ = SuffixElem s
2210+ | SuffixSuffix s
2211+ | SuffixYield a (SuffixState s a)
2212+
2213+{-# INLINE_NORMAL insertAfterEach #-}
2214+insertAfterEach :: forall m a. Monad m => m a -> Stream m a -> Stream m a
2215+insertAfterEach action (Stream step state) = Stream step' (SuffixElem state)
2216+ where
2217+ {-# INLINE_LATE step' #-}
2218+ step' gst (SuffixElem st) = do
2219+ r <- step gst st
2220+ return $ case r of
2221+ Yield x s -> Skip (SuffixYield x (SuffixSuffix s))
2222+ Skip s -> Skip (SuffixElem s)
2223+ Stop -> Stop
2224+
2225+ step' _ (SuffixSuffix st) = do
2226+ action >>= \r -> return $ Skip (SuffixYield r (SuffixElem st))
2227+
2228+ step' _ (SuffixYield x next) = return $ Yield x next
2229+
22072230{-# INLINE intersperse #-}
22082231intersperse :: Monad m => a -> Stream m a -> Stream m a
22092232intersperse a = intersperseM (return a)
src/Streamly/Streams/StreamD/Type.hs+8−1
@@ -65,7 +65,7 @@ module Streamly.Streams.StreamD.Type
6565where
6666
6767import Control.Applicative (liftA2)
68−import Control.Monad (ap)
68+import Control.Monad (ap, when)
6969import Control.Monad.Trans (lift, MonadTrans)
7070import GHC.Types (SPEC(..))
7171import Prelude hiding (map, mapM, foldr, take, concatMap)
@@ -520,6 +520,13 @@ groupsOf n f (Stream step state) =
520520
521521 {-# INLINE_LATE stepOuter #-}
522522 stepOuter gst (Just st) = do
523+ -- XXX shall we use the Natural type instead? Need to check performance
524+ -- implications.
525+ when (n <= 0) $
526+ -- XXX we can pass the module string from the higher level API
527+ error $ "Streamly.Streams.StreamD.Type.groupsOf: the size of "
528+ ++ "groups [" ++ show n ++ "] must be a natural number"
529+
523530 -- We retrieve the first element of the stream before we start to fold
524531 -- a chunk so that we do not return an empty chunk in case the stream
525532 -- is empty.
streamly.cabal+2
@@ -738,6 +738,7 @@ benchmark base
738738 , Streamly.Streams.StreamD.Type
739739 , Streamly.Streams.StreamD
740740 , Streamly.Streams.Prelude
741+ , Streamly.FileSystem.IOVec
741742
742743 , StreamDOps
743744 , StreamKOps
@@ -806,6 +807,7 @@ executable nano-bench
806807 , Streamly.Streams.StreamK.Type
807808 , Streamly.Streams.StreamK
808809 , Streamly.Streams.StreamD.Type
810+ , Streamly.FileSystem.IOVec
809811 , Streamly.Streams.StreamD
810812 default-language: Haskell2010
811813 ghc-options: -O2 -Wall -with-rtsopts "-T"
test/String.hs+23−3
@@ -3,9 +3,12 @@
33
44module Main (main) where
55
6−import Test.Hspec.QuickCheck
7−import Test.QuickCheck (Property, forAll, Gen, listOf, arbitraryUnicodeChar)
8−import Test.QuickCheck.Monadic (run, monadicIO, assert)
6+import Data.Char (ord, chr)
7+import Data.Word (Word8)
8+import Test.Hspec.QuickCheck
9+import Test.QuickCheck
10+ (Property, forAll, Gen, listOf, arbitraryUnicodeChar, arbitrary)
11+import Test.QuickCheck.Monadic (run, monadicIO, assert)
912
1013import Test.Hspec as H
1114
@@ -14,17 +17,22 @@ import qualified Streamly.Prelude as S
1417import qualified Streamly.String as SS
1518
1619-- Coverage build takes too long with default number of tests
20+{-
1721maxTestCount :: Int
1822#ifdef DEVBUILD
1923maxTestCount = 100
2024#else
2125maxTestCount = 10
2226#endif
27+-}
2328
2429-- Use quickcheck-unicode instead?
2530genUnicode :: Gen String
2631genUnicode = listOf arbitraryUnicodeChar
2732
33+genWord8 :: Gen [Word8]
34+genWord8 = listOf arbitrary
35+
2836propDecodeEncodeId :: Property
2937propDecodeEncodeId =
3038 forAll genUnicode $ \list ->
@@ -43,6 +51,17 @@ testLines =
4351 $ S.fromList list
4452 assert (xs == lines list)
4553
54+testLinesArray :: Property
55+testLinesArray =
56+ forAll genWord8 $ \list ->
57+ monadicIO $ do
58+ xs <- S.toList
59+ $ S.map A.toList
60+ $ A.splitArraysOn 10
61+ $ S.yield (A.fromList list)
62+ assert (xs == map (map (fromIntegral . ord))
63+ (lines (map (chr . fromIntegral) list)))
64+
4665testWords :: Property
4766testWords =
4867 forAll genUnicode $ \list ->
@@ -82,6 +101,7 @@ main = hspec
82101 describe "UTF8 - Encoding / Decoding" $ do
83102 prop "decodeUtf8 . encodeUtf8 == id" $ propDecodeEncodeId
84103 prop "Streamly.String.lines == Prelude.lines" $ testLines
104+ prop "Arrays Streamly.String.lines == Prelude.lines" $ testLinesArray
85105 prop "Streamly.String.words == Prelude.words" $ testWords
86106 prop
87107 "Streamly.String.unlines . Streamly.String.lines == unlines . lines"