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.Fold as FL
37import qualified Streamly.Internal.Data.Stream as IP
38import qualified Streamly.Internal.FileSystem.Handle as IFH
39import qualified Streamly.Internal.Unicode.Stream as IUS
40import qualified Streamly.Data.Stream.Prelude as S
41import qualified Streamly.Unicode.Stream as SS
43import Test.Tasty.Bench hiding (env)
44import Prelude hiding (last, length)
45import Streamly.Benchmark.Common.Handle
48import Streamly.Internal.Data.Stream (Step(..), FoldMany)
50import qualified Streamly.Internal.Data.MutArray as MutArray
51import qualified Streamly.Internal.Data.Stream as D
52import qualified Streamly.Internal.Data.Unfold as IUF
57-- TBD reading with unfold
59-------------------------------------------------------------------------------
61-------------------------------------------------------------------------------
63-- | Get the last byte from a file bytestream.
64readLast :: Handle -> IO (Maybe Word8)
65readLast = S.fold Fold.last . S.unfold FH.reader
68inspect $ hasNoTypeClasses 'readLast
69inspect $ 'readLast `hasNoType` ''Step -- S.unfold
70inspect $ 'readLast `hasNoType` ''IUF.ConcatState -- FH.read/UF.many
71inspect $ 'readLast `hasNoType` ''MutArray.ArrayUnsafe -- FH.read/A.read
74-- assert that flattenArrays constructors are not present
75-- | Count the number of bytes in a file.
76readCountBytes :: Handle -> IO Int
77readCountBytes = S.fold Fold.length . S.unfold FH.reader
80inspect $ hasNoTypeClasses 'readCountBytes
81inspect $ 'readCountBytes `hasNoType` ''Step -- S.unfold
82inspect $ 'readCountBytes `hasNoType` ''IUF.ConcatState -- FH.read/UF.many
83inspect $ 'readCountBytes `hasNoType` ''MutArray.ArrayUnsafe -- FH.read/A.read
86-- | Count the number of lines in a file.
87readCountLines :: Handle -> IO Int
95inspect $ hasNoTypeClasses 'readCountLines
96inspect $ 'readCountLines `hasNoType` ''Step
97inspect $ 'readCountLines `hasNoType` ''IUF.ConcatState -- FH.read/UF.many
98inspect $ 'readCountLines `hasNoType` ''MutArray.ArrayUnsafe -- FH.read/A.read
101-- | Count the number of words in a file.
102readCountWords :: Handle -> IO Int
110inspect $ hasNoTypeClasses 'readCountWords
111-- inspect $ 'readCountWords `hasNoType` ''Step
114-- | Sum the bytes in a file.
115readSumBytes :: Handle -> IO Word8
116readSumBytes = S.fold Fold.sum . S.unfold FH.reader
119inspect $ hasNoTypeClasses 'readSumBytes
120inspect $ 'readSumBytes `hasNoType` ''Step
121inspect $ 'readSumBytes `hasNoType` ''IUF.ConcatState -- FH.read/UF.many
122inspect $ 'readSumBytes `hasNoType` ''MutArray.ArrayUnsafe -- FH.read/A.read
125-- XXX When we mark this with INLINE and we have two benchmarks using S.drain
126-- in one benchmark group then somehow GHC ends up delaying the inlining of
127-- readDrain. Since S.drain has an INLINE[2] for proper rule firing, that does
128-- not work well because of delyaed inlining and the code does not fuse. We
129-- need some way of propagating the inline phase information up so that we can
130-- expedite inlining of the callers too automatically. The minimal example for
131-- the problem can be created by using just two benchmarks in a bench group
132-- both using "readDrain". Either GHC should be fixed or we can use
133-- fusion-plugin to propagate INLINE phase information such that this problem
135readDrain :: Handle -> IO ()
136readDrain inh = S.fold Fold.drain $ S.unfold FH.reader inh
138-- XXX investigate why we need an INLINE in this case (GHC)
139{-# INLINE readDecodeLatin1 #-}
140readDecodeLatin1 :: Handle -> IO ()
141readDecodeLatin1 inh =
144 $ S.unfold FH.reader inh
146readDecodeUtf8 :: Handle -> IO ()
150 $ S.unfold FH.reader inh
153inspect $ hasNoTypeClasses 'readDecodeUtf8
154-- inspect $ 'readDecodeUtf8Lax `hasNoType` ''Step
157o_1_space_reduce_read :: BenchEnv -> [Benchmark]
158o_1_space_reduce_read env =
159 [ bgroup "reduce/read"
160 [ -- read raw bytes without any decoding
161 mkBench "S.drain" env $ \inh _ ->
163 , mkBench "S.last" env $ \inh _ ->
165 , mkBench "S.sum" env $ \inh _ ->
168 -- read with Latin1 decoding
169 , mkBench "SS.decodeLatin1" env $ \inh _ ->
171 , mkBench "S.length" env $ \inh _ ->
173 , mkBench "US.lines . SS.decodeLatin1" env $ \inh _ ->
175 , mkBench "US.words . SS.decodeLatin1" env $ \inh _ ->
178 -- read with utf8 decoding
179 , mkBenchSmall "SS.decodeUtf8" env $ \inh _ ->
184-------------------------------------------------------------------------------
186-------------------------------------------------------------------------------
188-- | Count the number of lines in a file.
189getChunksConcatUnfoldCountLines :: Handle -> IO Int
190getChunksConcatUnfoldCountLines inh =
194 -- XXX replace with toBytes
195 $ S.unfoldMany A.reader (IFH.readChunks inh)
198inspect $ hasNoTypeClasses 'getChunksConcatUnfoldCountLines
199inspect $ 'getChunksConcatUnfoldCountLines `hasNoType` ''Step
200inspect $ 'getChunksConcatUnfoldCountLines `hasNoType` ''D.ConcatMapUState
203o_1_space_reduce_toBytes :: BenchEnv -> [Benchmark]
204o_1_space_reduce_toBytes env =
205 [ bgroup "reduce/toBytes"
206 [ mkBench "US.lines . SS.decodeLatin1" env $ \inh _ ->
207 getChunksConcatUnfoldCountLines inh
211-------------------------------------------------------------------------------
212-- reduce after grouping in chunks
213-------------------------------------------------------------------------------
215chunksOfSum :: Int -> Handle -> IO Int
217 S.fold Fold.length $ IP.groupsOf n FL.sum (S.unfold FH.reader inh)
219foldManyPostChunksOfSum :: Int -> Handle -> IO Int
220foldManyPostChunksOfSum n inh =
222 $ IP.foldManyPost (FL.take n FL.sum) (S.unfold FH.reader inh)
224foldManyChunksOfSum :: Int -> Handle -> IO Int
225foldManyChunksOfSum n inh =
227 $ IP.foldMany (FL.take n FL.sum) (S.unfold FH.reader inh)
229-- XXX investigate why we need an INLINE in this case (GHC)
230-- Even though allocations remain the same in both cases inlining improves time
232-- | Slice in chunks of size n and get the count of chunks.
233{-# INLINE groupsOf #-}
234groupsOf :: Int -> Handle -> IO Int
236 -- writeNUnsafe gives 2.5x boost here over writeN.
238 $ IP.groupsOf n (A.writeNUnsafe n) (S.unfold FH.reader inh)
241inspect $ hasNoTypeClasses 'groupsOf
242inspect $ 'groupsOf `hasNoType` ''Step
243inspect $ 'groupsOf `hasNoType` ''FoldMany
244inspect $ 'groupsOf `hasNoType` ''MutArray.ArrayUnsafe -- AT.writeNUnsafe
246inspect $ 'groupsOf `hasNoType` ''IUF.ConcatState -- FH.read/UF.many
249{-# INLINE chunksOf #-}
250chunksOf :: Int -> Handle -> IO Int
252 S.fold Fold.length $ Stream.chunksOf n (S.unfold FH.reader inh)
254o_1_space_reduce_read_grouped :: BenchEnv -> [Benchmark]
255o_1_space_reduce_read_grouped env =
256 [ bgroup "reduce/read/chunks"
257 [ mkBench ("S.groupsOf " ++ show (bigSize env) ++ " FL.sum") env $
259 chunksOfSum (bigSize env) inh
260 , mkBench "S.groupsOf 1 FL.sum" env $ \inh _ ->
263 -- XXX investigate why we need inline/noinline in these cases (GHC)
264 -- Chunk using parsers
266 ("S.foldManyPost (FL.take " ++ show (bigSize env) ++ " FL.sum)")
268 $ \inh _ -> noinline foldManyPostChunksOfSum (bigSize env) inh
270 "S.foldManyPost (FL.take 1 FL.sum)"
272 $ \inh _ -> inline foldManyPostChunksOfSum 1 inh
274 ("S.foldMany (FL.take " ++ show (bigSize env) ++ " FL.sum)")
276 $ \inh _ -> noinline foldManyChunksOfSum (bigSize env) inh
278 "S.foldMany (FL.take 1 FL.sum)"
280 $ \inh _ -> inline foldManyChunksOfSum 1 inh
282 -- folding chunks to arrays
283 , mkBenchSmall "S.groupsOf 1" env $ \inh _ ->
285 , mkBench "S.groupsOf 10" env $ \inh _ ->
287 , mkBench "S.groupsOf 1000" env $ \inh _ ->
290 -- chunksOf may use a different impl than groupsOf
291 , mkBenchSmall "S.chunksOf 1" env $ \inh _ ->
293 , mkBench "S.chunksOf 10" env $ \inh _ ->
295 , mkBench "S.chunksOf 1000" env $ \inh _ ->
300allBenchmarks :: BenchEnv -> [Benchmark]
301allBenchmarks env = Prelude.concat
302 [ o_1_space_reduce_read env
303 , o_1_space_reduce_toBytes env
304 , 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