on 95ebb7b help sign in
386 lines 13 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.Functor.Identity (runIdentity)
26import Data.Word (Word8)
27import GHC.Magic (inline)
28#if __GLASGOW_HASKELL__ >= 802
29import GHC.Magic (noinline)
30#else
31#define noinline
32#endif
33import System.IO (Handle)
35import qualified Streamly.FileSystem.Handle as FH
36import qualified Streamly.Internal.Data.Array.Storable.Foreign as A
37import qualified Streamly.Internal.Data.Array.Storable.Foreign.Types as AT
38import qualified Streamly.Internal.Data.Fold as FL
39import qualified Streamly.Internal.Data.Parser as PR
40import qualified Streamly.Internal.Data.Stream.IsStream as IP
41import qualified Streamly.Internal.FileSystem.Handle as IFH
42import qualified Streamly.Internal.Memory.ArrayStream as AS
43import qualified Streamly.Internal.Unicode.Stream as IUS
44import qualified Streamly.Prelude as S
45import qualified Streamly.Unicode.Stream as SS
47import Gauge hiding (env)
48import Prelude hiding (last, length)
49import Streamly.Benchmark.Common.Handle
51#ifdef INSPECTION
52import Streamly.Internal.Data.Stream.StreamD.Type (Step(..), GroupState)
54import qualified Streamly.Internal.Data.Array.Storable.Foreign.Mut.Types as MA
55import qualified Streamly.Internal.Data.Stream.StreamD as D
56import qualified Streamly.Internal.Data.Unfold as IUF
58import Test.Inspection
59#endif
61-------------------------------------------------------------------------------
62-- read chunked using toChunks
63-------------------------------------------------------------------------------
65-- | Get the last byte from a file bytestream.
66toChunksLast :: Handle -> IO (Maybe Word8)
67toChunksLast inh = do
68 let s = IFH.toChunks inh
69 larr <- S.last s
70 return $ case larr of
71 Nothing -> Nothing
72 Just arr -> A.readIndex arr (A.length arr - 1)
74#ifdef INSPECTION
75inspect $ hasNoTypeClasses 'toChunksLast
76inspect $ 'toChunksLast `hasNoType` ''Step
77#endif
79-- | Count the number of bytes in a file.
80toChunksSumLengths :: Handle -> IO Int
81toChunksSumLengths inh =
82 let s = IFH.toChunks inh
83 in S.sum (S.map A.length s)
85#ifdef INSPECTION
86inspect $ hasNoTypeClasses 'toChunksSumLengths
87inspect $ 'toChunksSumLengths `hasNoType` ''Step
88#endif
90-- | Count the number of lines in a file.
91toChunksSplitOnSuffix :: Handle -> IO Int
92toChunksSplitOnSuffix = S.length . AS.splitOnSuffix 10 . IFH.toChunks
94#ifdef INSPECTION
95inspect $ hasNoTypeClasses 'toChunksSplitOnSuffix
96inspect $ 'toChunksSplitOnSuffix `hasNoType` ''Step
97#endif
99-- XXX use a word splitting combinator instead of splitOn and test it.
100-- | Count the number of words in a file.
101toChunksSplitOn :: Handle -> IO Int
102toChunksSplitOn = S.length . AS.splitOn 32 . IFH.toChunks
104#ifdef INSPECTION
105inspect $ hasNoTypeClasses 'toChunksSplitOn
106inspect $ 'toChunksSplitOn `hasNoType` ''Step
107#endif
109-- | Sum the bytes in a file.
110toChunksCountBytes :: Handle -> IO Word8
111toChunksCountBytes inh = do
112 let foldlArr' f z = runIdentity . S.foldl' f z . A.toStream
113 let s = IFH.toChunks inh
114 S.foldl' (\acc arr -> acc + foldlArr' (+) 0 arr) 0 s
116#ifdef INSPECTION
117inspect $ hasNoTypeClasses 'toChunksCountBytes
118inspect $ 'toChunksCountBytes `hasNoType` ''Step
119#endif
121toChunksDecodeUtf8Arrays :: Handle -> IO ()
122toChunksDecodeUtf8Arrays =
123 S.drain . IUS.decodeUtf8Arrays . IFH.toChunks
125#ifdef INSPECTION
126inspect $ hasNoTypeClasses 'toChunksDecodeUtf8Arrays
127-- inspect $ 'toChunksDecodeUtf8ArraysLenient `hasNoType` ''Step
128#endif
130o_1_space_read_chunked :: BenchEnv -> [Benchmark]
131o_1_space_read_chunked env =
132 -- read using toChunks instead of read
133 [ bgroup "reduce/toChunks"
134 [ mkBench "S.last" env $ \inH _ ->
135 toChunksLast inH
136 -- Note: this cannot be fairly compared with GNU wc -c or wc -m as
137 -- wc uses lseek to just determine the file size rather than reading
138 -- and counting characters.
139 , mkBench "S.sum . S.map A.length" env $ \inH _ ->
140 toChunksSumLengths inH
141 , mkBench "AS.splitOnSuffix" env $ \inH _ ->
142 toChunksSplitOnSuffix inH
143 , mkBench "AS.splitOn" env $ \inH _ ->
144 toChunksSplitOn inH
145 , mkBench "countBytes" env $ \inH _ ->
146 toChunksCountBytes inH
147 , mkBenchSmall "US.decodeUtf8Arrays" env $ \inH _ ->
148 toChunksDecodeUtf8Arrays inH
149 ]
150 ]
152-- TBD reading with unfold
154-------------------------------------------------------------------------------
155-- unfold read
156-------------------------------------------------------------------------------
158-- | Get the last byte from a file bytestream.
159readLast :: Handle -> IO (Maybe Word8)
160readLast = S.last . S.unfold FH.read
162#ifdef INSPECTION
163inspect $ hasNoTypeClasses 'readLast
164inspect $ 'readLast `hasNoType` ''Step -- S.unfold
165inspect $ 'readLast `hasNoType` ''IUF.ConcatState -- FH.read/UF.concat
166inspect $ 'readLast `hasNoType` ''MA.ReadUState -- FH.read/A.read
167#endif
169-- assert that flattenArrays constructors are not present
170-- | Count the number of bytes in a file.
171readCountBytes :: Handle -> IO Int
172readCountBytes = S.length . S.unfold FH.read
174#ifdef INSPECTION
175inspect $ hasNoTypeClasses 'readCountBytes
176inspect $ 'readCountBytes `hasNoType` ''Step -- S.unfold
177inspect $ 'readCountBytes `hasNoType` ''IUF.ConcatState -- FH.read/UF.concat
178inspect $ 'readCountBytes `hasNoType` ''MA.ReadUState -- FH.read/A.read
179#endif
181-- | Count the number of lines in a file.
182readCountLines :: Handle -> IO Int
183readCountLines =
184 S.length
185 . IUS.lines FL.drain
186 . SS.decodeLatin1
187 . S.unfold FH.read
189#ifdef INSPECTION
190inspect $ hasNoTypeClasses 'readCountLines
191inspect $ 'readCountLines `hasNoType` ''Step
192inspect $ 'readCountLines `hasNoType` ''IUF.ConcatState -- FH.read/UF.concat
193inspect $ 'readCountLines `hasNoType` ''MA.ReadUState -- FH.read/A.read
194#endif
196-- | Count the number of words in a file.
197readCountWords :: Handle -> IO Int
198readCountWords =
199 S.length
200 . IUS.words FL.drain
201 . SS.decodeLatin1
202 . S.unfold FH.read
204#ifdef INSPECTION
205inspect $ hasNoTypeClasses 'readCountWords
206-- inspect $ 'readCountWords `hasNoType` ''Step
207#endif
209-- | Sum the bytes in a file.
210readSumBytes :: Handle -> IO Word8
211readSumBytes = S.sum . S.unfold FH.read
213#ifdef INSPECTION
214inspect $ hasNoTypeClasses 'readSumBytes
215inspect $ 'readSumBytes `hasNoType` ''Step
216inspect $ 'readSumBytes `hasNoType` ''IUF.ConcatState -- FH.read/UF.concat
217inspect $ 'readSumBytes `hasNoType` ''MA.ReadUState -- FH.read/A.read
218#endif
220-- XXX When we mark this with INLINE and we have two benchmarks using S.drain
221-- in one benchmark group then somehow GHC ends up delaying the inlining of
222-- readDrain. Since S.drain has an INLINE[2] for proper rule firing, that does
223-- not work well because of delyaed inlining and the code does not fuse. We
224-- need some way of propagating the inline phase information up so that we can
225-- expedite inlining of the callers too automatically. The minimal example for
226-- the problem can be created by using just two benchmarks in a bench group
227-- both using "readDrain". Either GHC should be fixed or we can use
228-- fusion-plugin to propagate INLINE phase information such that this problem
229-- does not occur.
230readDrain :: Handle -> IO ()
231readDrain inh = S.drain $ S.unfold FH.read inh
233-- XXX investigate why we need an INLINE in this case (GHC)
234{-# INLINE readDecodeLatin1 #-}
235readDecodeLatin1 :: Handle -> IO ()
236readDecodeLatin1 inh =
237 S.drain
238 $ SS.decodeLatin1
239 $ S.unfold FH.read inh
241readDecodeUtf8 :: Handle -> IO ()
242readDecodeUtf8 inh =
243 S.drain
244 $ SS.decodeUtf8
245 $ S.unfold FH.read inh
247#ifdef INSPECTION
248inspect $ hasNoTypeClasses 'readDecodeUtf8
249-- inspect $ 'readDecodeUtf8Lax `hasNoType` ''Step
250#endif
252o_1_space_reduce_read :: BenchEnv -> [Benchmark]
253o_1_space_reduce_read env =
254 [ bgroup "reduce/read"
255 [ -- read raw bytes without any decoding
256 mkBench "S.drain" env $ \inh _ ->
257 readDrain inh
258 , mkBench "S.last" env $ \inh _ ->
259 readLast inh
260 , mkBench "S.sum" env $ \inh _ ->
261 readSumBytes inh
263 -- read with Latin1 decoding
264 , mkBench "SS.decodeLatin1" env $ \inh _ ->
265 readDecodeLatin1 inh
266 , mkBench "S.length" env $ \inh _ ->
267 readCountBytes inh
268 , mkBench "US.lines . SS.decodeLatin1" env $ \inh _ ->
269 readCountLines inh
270 , mkBenchSmall "US.words . SS.decodeLatin1" env $ \inh _ ->
271 readCountWords inh
273 -- read with utf8 decoding
274 , mkBenchSmall "SS.decodeUtf8" env $ \inh _ ->
275 readDecodeUtf8 inh
276 ]
277 ]
279-------------------------------------------------------------------------------
280-- stream toBytes
281-------------------------------------------------------------------------------
283-- | Count the number of lines in a file.
284toChunksConcatUnfoldCountLines :: Handle -> IO Int
285toChunksConcatUnfoldCountLines inh =
286 S.length
287 $ IUS.lines FL.drain
288 $ SS.decodeLatin1
289 -- XXX replace with toBytes
290 $ S.concatUnfold A.read (IFH.toChunks inh)
292#ifdef INSPECTION
293inspect $ hasNoTypeClasses 'toChunksConcatUnfoldCountLines
294inspect $ 'toChunksConcatUnfoldCountLines `hasNoType` ''Step
295inspect $ 'toChunksConcatUnfoldCountLines `hasNoType` ''D.ConcatMapUState
296#endif
298o_1_space_reduce_toBytes :: BenchEnv -> [Benchmark]
299o_1_space_reduce_toBytes env =
300 [ bgroup "reduce/toBytes"
301 [ mkBench "US.lines . SS.decodeLatin1" env $ \inh _ ->
302 toChunksConcatUnfoldCountLines inh
303 ]
304 ]
306-------------------------------------------------------------------------------
307-- reduce after grouping in chunks
308-------------------------------------------------------------------------------
310chunksOfSum :: Int -> Handle -> IO Int
311chunksOfSum n inh = S.length $ S.chunksOf n FL.sum (S.unfold FH.read inh)
313foldManyChunksOfSum :: Int -> Handle -> IO Int
314foldManyChunksOfSum n inh =
315 S.length $ IP.foldMany (FL.takeLE n FL.sum) (S.unfold FH.read inh)
317parseManyChunksOfSum :: Int -> Handle -> IO Int
318parseManyChunksOfSum n inh =
319 S.length
320 $ IP.parseMany (PR.fromFold $ FL.takeLE n FL.sum) (S.unfold FH.read inh)
322-- XXX investigate why we need an INLINE in this case (GHC)
323-- Even though allocations remain the same in both cases inlining improves time
324-- by 4x.
325-- | Slice in chunks of size n and get the count of chunks.
326{-# INLINE chunksOf #-}
327chunksOf :: Int -> Handle -> IO Int
328chunksOf n inh =
329 -- writeNUnsafe gives 2.5x boost here over writeN.
330 -- XXX replace with S.arraysOf
331 S.length $ S.chunksOf n (AT.writeNUnsafe n) (S.unfold FH.read inh)
333#ifdef INSPECTION
334inspect $ hasNoTypeClasses 'chunksOf
335inspect $ 'chunksOf `hasNoType` ''Step
336inspect $ 'chunksOf `hasNoType` ''GroupState
337inspect $ 'chunksOf `hasNoType` ''AT.ArrayUnsafe -- AT.writeNUnsafe
338inspect $ 'chunksOf `hasNoType` ''IUF.ConcatState -- FH.read/UF.concat
339inspect $ 'chunksOf `hasNoType` ''MA.ReadUState -- FH.read/A.read
340#endif
342o_1_space_reduce_read_grouped :: BenchEnv -> [Benchmark]
343o_1_space_reduce_read_grouped env =
344 [ bgroup "reduce/read/chunks"
345 [ mkBench ("S.chunksOf " ++ show (bigSize env) ++ " FL.sum") env $
346 \inh _ ->
347 chunksOfSum (bigSize env) inh
348 , mkBench "S.chunksOf 1 FL.sum" env $ \inh _ ->
349 chunksOfSum 1 inh
351 -- XXX investigate why we need inline/noinline in these cases (GHC)
352 -- Chunk using parsers
353 , mkBenchSmall
354 ("S.foldMany (FL.take " ++ show (bigSize env) ++ " FL.sum)")
355 env
356 $ \inh _ -> noinline foldManyChunksOfSum (bigSize env) inh
357 , mkBench
358 "S.foldMany (FL.take 1 FL.sum)"
359 env
360 $ \inh _ -> inline foldManyChunksOfSum 1 inh
361 , mkBenchSmall
362 ("S.parseMany (FL.take " ++ show (bigSize env) ++ " FL.sum)")
363 env
364 $ \inh _ -> noinline parseManyChunksOfSum (bigSize env) inh
365 , mkBench
366 "S.parseMany (FL.take 1 FL.sum)"
367 env
368 $ \inh _ -> inline parseManyChunksOfSum 1 inh
370 -- folding chunks to arrays
371 , mkBenchSmall "S.arraysOf 1" env $ \inh _ ->
372 chunksOf 1 inh
373 , mkBench "S.arraysOf 10" env $ \inh _ ->
374 chunksOf 10 inh
375 , mkBench "S.arraysOf 1000" env $ \inh _ ->
376 chunksOf 1000 inh
377 ]
378 ]
380allBenchmarks :: BenchEnv -> [Benchmark]
381allBenchmarks env = Prelude.concat
382 [ o_1_space_read_chunked env
383 , o_1_space_reduce_read env
384 , o_1_space_reduce_toBytes env
385 , o_1_space_reduce_read_grouped env
386 ]