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