1{-# OPTIONS_GHC -Wno-deprecations #-}
4-- Module : Streamly.Benchmark.FileSystem.Handle
5-- Copyright : (c) 2019 Composewell Technologies
6-- License : BSD-3-Clause
7-- Maintainer : streamly@composewell.com
8-- Stability : experimental
12{-# LANGUAGE ScopedTypeVariables #-}
14#ifdef __HADDOCK_VERSION__
19{-# LANGUAGE TemplateHaskell #-}
20{-# OPTIONS_GHC -fplugin Test.Inspection.Plugin #-}
27import Data.Word (Word8)
28import GHC.Magic (inline)
29import GHC.Magic (noinline)
30import System.IO (Handle)
32import qualified Streamly.Data.Stream as Stream
33import qualified Streamly.Data.Fold as Fold
34import qualified Streamly.FileSystem.Handle as FH
35import qualified Streamly.Internal.Data.Array as A
36import qualified Streamly.Internal.Data.Array as AT
37import qualified Streamly.Internal.Data.Fold as FL
38import qualified Streamly.Internal.Data.Stream as IP
39import qualified Streamly.Internal.FileSystem.Handle as IFH
40import qualified Streamly.Internal.Unicode.Stream as IUS
41import qualified Streamly.Data.Stream.Prelude as S
42import qualified Streamly.Unicode.Stream as SS
44import Test.Tasty.Bench hiding (env)
45import Prelude hiding (last, length)
46import Streamly.Benchmark.Common.Handle
49import Streamly.Internal.Data.Stream (Step(..), FoldMany)
51import qualified Streamly.Internal.Data.MutArray as MA
52import qualified Streamly.Internal.Data.Stream as D
53import qualified Streamly.Internal.Data.Unfold as IUF
58-- TBD reading with unfold
60-------------------------------------------------------------------------------
62-------------------------------------------------------------------------------
64-- | Get the last byte from a file bytestream.
65readLast :: Handle -> IO (Maybe Word8)
66readLast = S.fold Fold.last . S.unfold FH.reader
69inspect $ hasNoTypeClasses 'readLast
70inspect $ 'readLast `hasNoType` ''Step -- S.unfold
71inspect $ 'readLast `hasNoType` ''IUF.ConcatState -- FH.read/UF.many
72inspect $ 'readLast `hasNoType` ''MA.ArrayUnsafe -- FH.read/A.read
75-- assert that flattenArrays constructors are not present
76-- | Count the number of bytes in a file.
77readCountBytes :: Handle -> IO Int
78readCountBytes = S.fold Fold.length . S.unfold FH.reader
81inspect $ hasNoTypeClasses 'readCountBytes
82inspect $ 'readCountBytes `hasNoType` ''Step -- S.unfold
83inspect $ 'readCountBytes `hasNoType` ''IUF.ConcatState -- FH.read/UF.many
84inspect $ 'readCountBytes `hasNoType` ''MA.ArrayUnsafe -- FH.read/A.read
87-- | Count the number of lines in a file.
88readCountLines :: Handle -> IO Int
96inspect $ hasNoTypeClasses 'readCountLines
97inspect $ 'readCountLines `hasNoType` ''Step
98inspect $ 'readCountLines `hasNoType` ''IUF.ConcatState -- FH.read/UF.many
99inspect $ 'readCountLines `hasNoType` ''MA.ArrayUnsafe -- FH.read/A.read
102-- | Count the number of words in a file.
103readCountWords :: Handle -> IO Int
111inspect $ hasNoTypeClasses 'readCountWords
112-- inspect $ 'readCountWords `hasNoType` ''Step
115-- | Sum the bytes in a file.
116readSumBytes :: Handle -> IO Word8
117readSumBytes = S.fold Fold.sum . S.unfold FH.reader
120inspect $ hasNoTypeClasses 'readSumBytes
121inspect $ 'readSumBytes `hasNoType` ''Step
122inspect $ 'readSumBytes `hasNoType` ''IUF.ConcatState -- FH.read/UF.many
123inspect $ 'readSumBytes `hasNoType` ''MA.ArrayUnsafe -- FH.read/A.read
126-- XXX When we mark this with INLINE and we have two benchmarks using S.drain
127-- in one benchmark group then somehow GHC ends up delaying the inlining of
128-- readDrain. Since S.drain has an INLINE[2] for proper rule firing, that does
129-- not work well because of delyaed inlining and the code does not fuse. We
130-- need some way of propagating the inline phase information up so that we can
131-- expedite inlining of the callers too automatically. The minimal example for
132-- the problem can be created by using just two benchmarks in a bench group
133-- both using "readDrain". Either GHC should be fixed or we can use
134-- fusion-plugin to propagate INLINE phase information such that this problem
136readDrain :: Handle -> IO ()
137readDrain inh = S.fold Fold.drain $ S.unfold FH.reader inh
139-- XXX investigate why we need an INLINE in this case (GHC)
140{-# INLINE readDecodeLatin1 #-}
141readDecodeLatin1 :: Handle -> IO ()
142readDecodeLatin1 inh =
145 $ S.unfold FH.reader inh
147readDecodeUtf8 :: Handle -> IO ()
151 $ S.unfold FH.reader inh
154inspect $ hasNoTypeClasses 'readDecodeUtf8
155-- inspect $ 'readDecodeUtf8Lax `hasNoType` ''Step
158o_1_space_reduce_read :: BenchEnv -> [Benchmark]
159o_1_space_reduce_read env =
160 [ bgroup "reduce/read"
161 [ -- read raw bytes without any decoding
162 mkBench "S.drain" env $ \inh _ ->
164 , mkBench "S.last" env $ \inh _ ->
166 , mkBench "S.sum" env $ \inh _ ->
169 -- read with Latin1 decoding
170 , mkBench "SS.decodeLatin1" env $ \inh _ ->
172 , mkBench "S.length" env $ \inh _ ->
174 , mkBench "US.lines . SS.decodeLatin1" env $ \inh _ ->
176 , mkBench "US.words . SS.decodeLatin1" env $ \inh _ ->
179 -- read with utf8 decoding
180 , mkBenchSmall "SS.decodeUtf8" env $ \inh _ ->
185-------------------------------------------------------------------------------
187-------------------------------------------------------------------------------
189-- | Count the number of lines in a file.
190getChunksConcatUnfoldCountLines :: Handle -> IO Int
191getChunksConcatUnfoldCountLines inh =
195 -- XXX replace with toBytes
196 $ S.unfoldMany A.reader (IFH.readChunks inh)
199inspect $ hasNoTypeClasses 'getChunksConcatUnfoldCountLines
200inspect $ 'getChunksConcatUnfoldCountLines `hasNoType` ''Step
201inspect $ 'getChunksConcatUnfoldCountLines `hasNoType` ''D.ConcatMapUState
204o_1_space_reduce_toBytes :: BenchEnv -> [Benchmark]
205o_1_space_reduce_toBytes env =
206 [ bgroup "reduce/toBytes"
207 [ mkBench "US.lines . SS.decodeLatin1" env $ \inh _ ->
208 getChunksConcatUnfoldCountLines inh
212-------------------------------------------------------------------------------
213-- reduce after grouping in chunks
214-------------------------------------------------------------------------------
216chunksOfSum :: Int -> Handle -> IO Int
218 S.fold Fold.length $ IP.groupsOf n FL.sum (S.unfold FH.reader inh)
220foldManyPostChunksOfSum :: Int -> Handle -> IO Int
221foldManyPostChunksOfSum n inh =
223 $ IP.foldManyPost (FL.take n FL.sum) (S.unfold FH.reader inh)
225foldManyChunksOfSum :: Int -> Handle -> IO Int
226foldManyChunksOfSum n inh =
228 $ IP.foldMany (FL.take n FL.sum) (S.unfold FH.reader inh)
230-- XXX investigate why we need an INLINE in this case (GHC)
231-- Even though allocations remain the same in both cases inlining improves time
233-- | Slice in chunks of size n and get the count of chunks.
234{-# INLINE groupsOf #-}
235groupsOf :: Int -> Handle -> IO Int
237 -- writeNUnsafe gives 2.5x boost here over writeN.
239 $ IP.groupsOf n (AT.writeNUnsafe n) (S.unfold FH.reader inh)
242inspect $ hasNoTypeClasses 'groupsOf
243inspect $ 'groupsOf `hasNoType` ''Step
244inspect $ 'groupsOf `hasNoType` ''FoldMany
245inspect $ 'groupsOf `hasNoType` ''AT.ArrayUnsafe -- AT.writeNUnsafe
247inspect $ 'groupsOf `hasNoType` ''IUF.ConcatState -- FH.read/UF.many
250{-# INLINE chunksOf #-}
251chunksOf :: Int -> Handle -> IO Int
253 S.fold Fold.length $ Stream.chunksOf n (S.unfold FH.reader inh)
255o_1_space_reduce_read_grouped :: BenchEnv -> [Benchmark]
256o_1_space_reduce_read_grouped env =
257 [ bgroup "reduce/read/chunks"
258 [ mkBench ("S.groupsOf " ++ show (bigSize env) ++ " FL.sum") env $
260 chunksOfSum (bigSize env) inh
261 , mkBench "S.groupsOf 1 FL.sum" env $ \inh _ ->
264 -- XXX investigate why we need inline/noinline in these cases (GHC)
265 -- Chunk using parsers
267 ("S.foldManyPost (FL.take " ++ show (bigSize env) ++ " FL.sum)")
269 $ \inh _ -> noinline foldManyPostChunksOfSum (bigSize env) inh
271 "S.foldManyPost (FL.take 1 FL.sum)"
273 $ \inh _ -> inline foldManyPostChunksOfSum 1 inh
275 ("S.foldMany (FL.take " ++ show (bigSize env) ++ " FL.sum)")
277 $ \inh _ -> noinline foldManyChunksOfSum (bigSize env) inh
279 "S.foldMany (FL.take 1 FL.sum)"
281 $ \inh _ -> inline foldManyChunksOfSum 1 inh
283 -- folding chunks to arrays
284 , mkBenchSmall "S.groupsOf 1" env $ \inh _ ->
286 , mkBench "S.groupsOf 10" env $ \inh _ ->
288 , mkBench "S.groupsOf 1000" env $ \inh _ ->
291 -- chunksOf may use a different impl than groupsOf
292 , mkBenchSmall "S.chunksOf 1" env $ \inh _ ->
294 , mkBench "S.chunksOf 10" env $ \inh _ ->
296 , mkBench "S.chunksOf 1000" env $ \inh _ ->
301allBenchmarks :: BenchEnv -> [Benchmark]
302allBenchmarks env = Prelude.concat
303 [ o_1_space_reduce_read env
304 , o_1_space_reduce_toBytes env
305 , o_1_space_reduce_read_grouped env
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list