on 4e604ef help sign in
297 lines 10 kB history raw
1-- |
2-- Module : Streamly.Benchmark.FileSystem.Handle
3-- Copyright : (c) 2019 Composewell Technologies
4-- License : BSD-3-Clause
5-- Maintainer : streamly@composewell.com
6-- Stability : experimental
7-- Portability : GHC
9{-# LANGUAGE CPP #-}
10{-# LANGUAGE ScopedTypeVariables #-}
12#ifdef __HADDOCK_VERSION__
13#undef INSPECTION
14#endif
16#ifdef INSPECTION
17{-# LANGUAGE TemplateHaskell #-}
18{-# OPTIONS_GHC -fplugin Test.Inspection.Plugin #-}
19#endif
21module Handle.Read
22 (allBenchmarks)
23where
25import Data.Word (Word8)
26import GHC.Magic (inline)
27import GHC.Magic (noinline)
28import System.IO (Handle)
30import qualified Streamly.FileSystem.Handle as FH
31import qualified Streamly.Internal.Data.Array.Foreign as A
32import qualified Streamly.Internal.Data.Array.Foreign.Type as AT
33import qualified Streamly.Internal.Data.Fold as FL
34import qualified Streamly.Internal.Data.Stream.IsStream as IP
35import qualified Streamly.Internal.FileSystem.Handle as IFH
36import qualified Streamly.Internal.Unicode.Stream as IUS
37import qualified Streamly.Prelude as S
38import qualified Streamly.Unicode.Stream as SS
40import Gauge hiding (env)
41import Prelude hiding (last, length)
42import Streamly.Benchmark.Common.Handle
44#ifdef INSPECTION
45import Streamly.Internal.Data.Stream.StreamD.Type (Step(..), FoldMany)
47import qualified Streamly.Internal.Data.Array.Foreign.Mut.Type as MA
48import qualified Streamly.Internal.Data.Stream.StreamD as D
49import qualified Streamly.Internal.Data.Unfold as IUF
51import Test.Inspection
52#endif
54-- TBD reading with unfold
56-------------------------------------------------------------------------------
57-- unfold read
58-------------------------------------------------------------------------------
60-- | Get the last byte from a file bytestream.
61readLast :: Handle -> IO (Maybe Word8)
62readLast = S.last . S.unfold FH.read
64#ifdef INSPECTION
65inspect $ hasNoTypeClasses 'readLast
66inspect $ 'readLast `hasNoType` ''Step -- S.unfold
67inspect $ 'readLast `hasNoType` ''IUF.ConcatState -- FH.read/UF.many
68inspect $ 'readLast `hasNoType` ''MA.ReadUState -- FH.read/A.read
69#endif
71-- assert that flattenArrays constructors are not present
72-- | Count the number of bytes in a file.
73readCountBytes :: Handle -> IO Int
74readCountBytes = S.length . S.unfold FH.read
76#ifdef INSPECTION
77inspect $ hasNoTypeClasses 'readCountBytes
78inspect $ 'readCountBytes `hasNoType` ''Step -- S.unfold
79inspect $ 'readCountBytes `hasNoType` ''IUF.ConcatState -- FH.read/UF.many
80inspect $ 'readCountBytes `hasNoType` ''MA.ReadUState -- FH.read/A.read
81#endif
83-- | Count the number of lines in a file.
84readCountLines :: Handle -> IO Int
85readCountLines =
86 S.length
87 . IUS.lines FL.drain
88 . SS.decodeLatin1
89 . S.unfold FH.read
91#ifdef INSPECTION
92inspect $ hasNoTypeClasses 'readCountLines
93inspect $ 'readCountLines `hasNoType` ''Step
94inspect $ 'readCountLines `hasNoType` ''IUF.ConcatState -- FH.read/UF.many
95inspect $ 'readCountLines `hasNoType` ''MA.ReadUState -- FH.read/A.read
96#endif
98-- | Count the number of words in a file.
99readCountWords :: Handle -> IO Int
100readCountWords =
101 S.length
102 . IUS.words FL.drain
103 . SS.decodeLatin1
104 . S.unfold FH.read
106#ifdef INSPECTION
107inspect $ hasNoTypeClasses 'readCountWords
108-- inspect $ 'readCountWords `hasNoType` ''Step
109#endif
111-- | Sum the bytes in a file.
112readSumBytes :: Handle -> IO Word8
113readSumBytes = S.sum . S.unfold FH.read
115#ifdef INSPECTION
116inspect $ hasNoTypeClasses 'readSumBytes
117inspect $ 'readSumBytes `hasNoType` ''Step
118inspect $ 'readSumBytes `hasNoType` ''IUF.ConcatState -- FH.read/UF.many
119inspect $ 'readSumBytes `hasNoType` ''MA.ReadUState -- FH.read/A.read
120#endif
122-- XXX When we mark this with INLINE and we have two benchmarks using S.drain
123-- in one benchmark group then somehow GHC ends up delaying the inlining of
124-- readDrain. Since S.drain has an INLINE[2] for proper rule firing, that does
125-- not work well because of delyaed inlining and the code does not fuse. We
126-- need some way of propagating the inline phase information up so that we can
127-- expedite inlining of the callers too automatically. The minimal example for
128-- the problem can be created by using just two benchmarks in a bench group
129-- both using "readDrain". Either GHC should be fixed or we can use
130-- fusion-plugin to propagate INLINE phase information such that this problem
131-- does not occur.
132readDrain :: Handle -> IO ()
133readDrain inh = S.drain $ S.unfold FH.read inh
135-- XXX investigate why we need an INLINE in this case (GHC)
136{-# INLINE readDecodeLatin1 #-}
137readDecodeLatin1 :: Handle -> IO ()
138readDecodeLatin1 inh =
139 S.drain
140 $ SS.decodeLatin1
141 $ S.unfold FH.read inh
143readDecodeUtf8 :: Handle -> IO ()
144readDecodeUtf8 inh =
145 S.drain
146 $ SS.decodeUtf8
147 $ S.unfold FH.read inh
149#ifdef INSPECTION
150inspect $ hasNoTypeClasses 'readDecodeUtf8
151-- inspect $ 'readDecodeUtf8Lax `hasNoType` ''Step
152#endif
154o_1_space_reduce_read :: BenchEnv -> [Benchmark]
155o_1_space_reduce_read env =
156 [ bgroup "reduce/read"
157 [ -- read raw bytes without any decoding
158 mkBench "S.drain" env $ \inh _ ->
159 readDrain inh
160 , mkBench "S.last" env $ \inh _ ->
161 readLast inh
162 , mkBench "S.sum" env $ \inh _ ->
163 readSumBytes inh
165 -- read with Latin1 decoding
166 , mkBench "SS.decodeLatin1" env $ \inh _ ->
167 readDecodeLatin1 inh
168 , mkBench "S.length" env $ \inh _ ->
169 readCountBytes inh
170 , mkBench "US.lines . SS.decodeLatin1" env $ \inh _ ->
171 readCountLines inh
172 , mkBench "US.words . SS.decodeLatin1" env $ \inh _ ->
173 readCountWords inh
175 -- read with utf8 decoding
176 , mkBenchSmall "SS.decodeUtf8" env $ \inh _ ->
177 readDecodeUtf8 inh
178 ]
179 ]
181-------------------------------------------------------------------------------
182-- stream toBytes
183-------------------------------------------------------------------------------
185-- | Count the number of lines in a file.
186getChunksConcatUnfoldCountLines :: Handle -> IO Int
187getChunksConcatUnfoldCountLines inh =
188 S.length
189 $ IUS.lines FL.drain
190 $ SS.decodeLatin1
191 -- XXX replace with toBytes
192 $ S.unfoldMany A.read (IFH.getChunks inh)
194#ifdef INSPECTION
195inspect $ hasNoTypeClasses 'getChunksConcatUnfoldCountLines
196inspect $ 'getChunksConcatUnfoldCountLines `hasNoType` ''Step
197inspect $ 'getChunksConcatUnfoldCountLines `hasNoType` ''D.ConcatMapUState
198#endif
200o_1_space_reduce_toBytes :: BenchEnv -> [Benchmark]
201o_1_space_reduce_toBytes env =
202 [ bgroup "reduce/toBytes"
203 [ mkBench "US.lines . SS.decodeLatin1" env $ \inh _ ->
204 getChunksConcatUnfoldCountLines inh
205 ]
206 ]
208-------------------------------------------------------------------------------
209-- reduce after grouping in chunks
210-------------------------------------------------------------------------------
212chunksOfSum :: Int -> Handle -> IO Int
213chunksOfSum n inh = S.length $ S.chunksOf n FL.sum (S.unfold FH.read inh)
215foldManyPostChunksOfSum :: Int -> Handle -> IO Int
216foldManyPostChunksOfSum n inh =
217 S.length $ IP.foldManyPost (FL.take n FL.sum) (S.unfold FH.read inh)
219foldManyChunksOfSum :: Int -> Handle -> IO Int
220foldManyChunksOfSum n inh =
221 S.length $ IP.foldMany (FL.take n FL.sum) (S.unfold FH.read inh)
223-- XXX investigate why we need an INLINE in this case (GHC)
224-- Even though allocations remain the same in both cases inlining improves time
225-- by 4x.
226-- | Slice in chunks of size n and get the count of chunks.
227{-# INLINE chunksOf #-}
228chunksOf :: Int -> Handle -> IO Int
229chunksOf n inh =
230 -- writeNUnsafe gives 2.5x boost here over writeN.
231 S.length $ S.chunksOf n (AT.writeNUnsafe n) (S.unfold FH.read inh)
233#ifdef INSPECTION
234inspect $ hasNoTypeClasses 'chunksOf
235inspect $ 'chunksOf `hasNoType` ''Step
236inspect $ 'chunksOf `hasNoType` ''FoldMany
237inspect $ 'chunksOf `hasNoType` ''AT.ArrayUnsafe -- AT.writeNUnsafe
238inspect $ 'chunksOf `hasNoType` ''IUF.ConcatState -- FH.read/UF.many
239inspect $ 'chunksOf `hasNoType` ''MA.ReadUState -- FH.read/A.read
240#endif
242{-# INLINE arraysOf #-}
243arraysOf :: Int -> Handle -> IO Int
244arraysOf n inh = S.length $ IP.arraysOf n (S.unfold FH.read inh)
246o_1_space_reduce_read_grouped :: BenchEnv -> [Benchmark]
247o_1_space_reduce_read_grouped env =
248 [ bgroup "reduce/read/chunks"
249 [ mkBench ("S.chunksOf " ++ show (bigSize env) ++ " FL.sum") env $
250 \inh _ ->
251 chunksOfSum (bigSize env) inh
252 , mkBench "S.chunksOf 1 FL.sum" env $ \inh _ ->
253 chunksOfSum 1 inh
255 -- XXX investigate why we need inline/noinline in these cases (GHC)
256 -- Chunk using parsers
257 , mkBench
258 ("S.foldManyPost (FL.take " ++ show (bigSize env) ++ " FL.sum)")
259 env
260 $ \inh _ -> noinline foldManyPostChunksOfSum (bigSize env) inh
261 , mkBench
262 "S.foldManyPost (FL.take 1 FL.sum)"
263 env
264 $ \inh _ -> inline foldManyPostChunksOfSum 1 inh
265 , mkBench
266 ("S.foldMany (FL.take " ++ show (bigSize env) ++ " FL.sum)")
267 env
268 $ \inh _ -> noinline foldManyChunksOfSum (bigSize env) inh
269 , mkBench
270 "S.foldMany (FL.take 1 FL.sum)"
271 env
272 $ \inh _ -> inline foldManyChunksOfSum 1 inh
274 -- folding chunks to arrays
275 , mkBenchSmall "S.chunksOf 1" env $ \inh _ ->
276 chunksOf 1 inh
277 , mkBench "S.chunksOf 10" env $ \inh _ ->
278 chunksOf 10 inh
279 , mkBench "S.chunksOf 1000" env $ \inh _ ->
280 chunksOf 1000 inh
282 -- arraysOf may use a different impl than chunksOf
283 , mkBenchSmall "S.arraysOf 1" env $ \inh _ ->
284 arraysOf 1 inh
285 , mkBench "S.arraysOf 10" env $ \inh _ ->
286 arraysOf 10 inh
287 , mkBench "S.arraysOf 1000" env $ \inh _ ->
288 arraysOf 1000 inh
289 ]
290 ]
292allBenchmarks :: BenchEnv -> [Benchmark]
293allBenchmarks env = Prelude.concat
294 [ o_1_space_reduce_read env
295 , o_1_space_reduce_toBytes env
296 , o_1_space_reduce_read_grouped env
297 ]