on fbec11f help sign in
575 lines 18 kB history raw
1-- |
2-- Module : Streamly.Benchmark.FileIO.Stream
3-- Copyright : (c) 2019 Composewell Technologies
4--
5-- License : BSD3
6-- Maintainer : harendra.kumar@gmail.com
7-- Stability : experimental
8-- Portability : GHC
10{-# LANGUAGE CPP #-}
11{-# LANGUAGE ScopedTypeVariables #-}
13#ifdef __HADDOCK_VERSION__
14#undef INSPECTION
15#endif
17#ifdef INSPECTION
18{-# LANGUAGE TemplateHaskell #-}
19{-# OPTIONS_GHC -fplugin Test.Inspection.Plugin #-}
20#endif
22module Streamly.Benchmark.FileIO.Stream
23 (
24 -- * FileIO
25 last
26 , countBytes
27 , countLines
28 , countLinesU
29 , countWords
30 , sumBytes
31 , cat
32 , catStreamWrite
33 , catBracket
34 , catBracketStream
35 , catOnException
36 , catOnExceptionStream
37 , catHandle
38 , catHandleStream
39 , catFinally
40 , catFinallyStream
41 , copy
42 , linesUnlinesCopy
43 , wordsUnwordsCopyWord8
44 , wordsUnwordsCopy
45 , readWord8
46 , decodeChar8
47 , copyCodecChar8
48 , copyCodecUtf8
49 , decodeUtf8Lenient
50 , copyCodecUtf8Lenient
51 , chunksOf
52 , chunksOfD
53 , splitOn
54 , splitOnSuffix
55 , wordsBy
56 , splitOnSeq
57 , splitOnSeqUtf8
58 , splitOnSuffixSeq
59 )
60where
62import Control.Exception (SomeException)
63import Data.Char (ord, chr)
64import Data.Word (Word8)
65import System.IO (Handle, hClose)
66import Prelude hiding (last, length)
68import qualified Streamly.FileSystem.Handle as FH
69import qualified Streamly.Internal.FileSystem.Handle as IFH
70import qualified Streamly.Memory.Array as A
71import qualified Streamly.Internal.Memory.Array.Types as AT
72import qualified Streamly.Prelude as S
73import qualified Streamly.Data.Fold as FL
74import qualified Streamly.Data.String as SS
75import qualified Streamly.Internal.Data.Unfold as IUF
76import qualified Streamly.Internal.Prelude as IP
77import qualified Streamly.Streams.StreamD as D
79#ifdef INSPECTION
80import Foreign.Storable (Storable)
81import Streamly.Streams.StreamD.Type (Step(..), GroupState)
82import Test.Inspection
83#endif
85-- | Get the last byte from a file bytestream.
86{-# INLINE last #-}
87last :: Handle -> IO (Maybe Word8)
88last = S.last . S.unfold FH.read
90#ifdef INSPECTION
91inspect $ hasNoTypeClasses 'last
92inspect $ 'last `hasNoType` ''Step
93inspect $ 'last `hasNoType` ''AT.FlattenState
94inspect $ 'last `hasNoType` ''D.ConcatMapUState
95#endif
97-- assert that flattenArrays constructors are not present
98-- | Count the number of bytes in a file.
99{-# INLINE countBytes #-}
100countBytes :: Handle -> IO Int
101countBytes = S.length . S.unfold FH.read
103#ifdef INSPECTION
104inspect $ hasNoTypeClasses 'countBytes
105inspect $ 'countBytes `hasNoType` ''Step
106inspect $ 'countBytes `hasNoType` ''AT.FlattenState
107inspect $ 'countBytes `hasNoType` ''D.ConcatMapUState
108#endif
110-- | Count the number of lines in a file.
111{-# INLINE countLines #-}
112countLines :: Handle -> IO Int
113countLines =
114 S.length
115 . SS.foldLines FL.drain
116 . SS.decodeChar8
117 . S.unfold FH.read
119#ifdef INSPECTION
120inspect $ hasNoTypeClasses 'countLines
121inspect $ 'countLines `hasNoType` ''Step
122inspect $ 'countLines `hasNoType` ''AT.FlattenState
123inspect $ 'countLines `hasNoType` ''D.ConcatMapUState
124#endif
126-- | Count the number of words in a file.
127{-# INLINE countWords #-}
128countWords :: Handle -> IO Int
129countWords =
130 S.length
131 . SS.foldWords FL.drain
132 . SS.decodeChar8
133 . S.unfold FH.read
135#ifdef INSPECTION
136inspect $ hasNoTypeClasses 'countWords
137-- inspect $ 'countWords `hasNoType` ''Step
138-- inspect $ 'countWords `hasNoType` ''D.ConcatMapUState
139#endif
141-- | Count the number of lines in a file.
142{-# INLINE countLinesU #-}
143countLinesU :: Handle -> IO Int
144countLinesU inh =
145 S.length
146 $ SS.foldLines FL.drain
147 $ SS.decodeChar8
148 $ S.concatUnfold A.read (IFH.toStreamArrays inh)
150#ifdef INSPECTION
151inspect $ hasNoTypeClasses 'countLinesU
152inspect $ 'countLinesU `hasNoType` ''Step
153inspect $ 'countLinesU `hasNoType` ''D.ConcatMapUState
154#endif
156-- | Sum the bytes in a file.
157{-# INLINE sumBytes #-}
158sumBytes :: Handle -> IO Word8
159sumBytes = S.sum . S.unfold FH.read
161#ifdef INSPECTION
162inspect $ hasNoTypeClasses 'sumBytes
163inspect $ 'sumBytes `hasNoType` ''Step
164inspect $ 'sumBytes `hasNoType` ''AT.FlattenState
165inspect $ 'sumBytes `hasNoType` ''D.ConcatMapUState
166#endif
168-- | Send the file contents to /dev/null
169{-# INLINE cat #-}
170cat :: Handle -> Handle -> IO ()
171cat devNull inh = S.fold (FH.write devNull) $ S.unfold FH.read inh
173#ifdef INSPECTION
174inspect $ hasNoTypeClasses 'cat
175inspect $ 'cat `hasNoType` ''Step
176inspect $ 'cat `hasNoType` ''AT.FlattenState
177inspect $ 'cat `hasNoType` ''D.ConcatMapUState
178#endif
180-- | Send the file contents to /dev/null
181{-# INLINE catStreamWrite #-}
182catStreamWrite :: Handle -> Handle -> IO ()
183catStreamWrite devNull inh = IFH.fromStream devNull $ S.unfold FH.read inh
185#ifdef INSPECTION
186inspect $ hasNoTypeClasses 'catStreamWrite
187inspect $ 'catStreamWrite `hasNoType` ''Step
188inspect $ 'catStreamWrite `hasNoType` ''AT.FlattenState
189inspect $ 'catStreamWrite `hasNoType` ''D.ConcatMapUState
190#endif
192-- | Send the file contents to /dev/null with exception handling
193{-# INLINE catBracket #-}
194catBracket :: Handle -> Handle -> IO ()
195catBracket devNull inh =
196 let readEx = IUF.bracket return (\_ -> hClose inh) FH.read
197 in S.fold (FH.write devNull) $ S.unfold readEx inh
199#ifdef INSPECTION
200inspect $ hasNoTypeClasses 'catBracket
201-- inspect $ 'catBracket `hasNoType` ''Step
202-- inspect $ 'catBracket `hasNoType` ''AT.FlattenState
203-- inspect $ 'catBracket `hasNoType` ''D.ConcatMapUState
204#endif
206-- | Send the file contents to /dev/null with exception handling
207{-# INLINE catBracketStream #-}
208catBracketStream :: Handle -> Handle -> IO ()
209catBracketStream devNull inh =
210 let readEx = S.bracket (return ()) (\_ -> hClose inh)
211 (\_ -> IFH.toStream inh)
212 in IFH.fromStream devNull $ readEx
214#ifdef INSPECTION
215inspect $ hasNoTypeClasses 'catBracketStream
216-- inspect $ 'catBracketStream `hasNoType` ''Step
217#endif
219-- | Send the file contents to /dev/null with exception handling
220{-# INLINE catOnException #-}
221catOnException :: Handle -> Handle -> IO ()
222catOnException devNull inh =
223 let readEx = IUF.onException (\_ -> hClose inh) FH.read
224 in S.fold (FH.write devNull) $ S.unfold readEx inh
226#ifdef INSPECTION
227inspect $ hasNoTypeClasses 'catOnException
228-- inspect $ 'catOnException `hasNoType` ''Step
229-- inspect $ 'catOnException `hasNoType` ''AT.FlattenState
230-- inspect $ 'catOnException `hasNoType` ''D.ConcatMapUState
231#endif
233-- | Send the file contents to /dev/null with exception handling
234{-# INLINE catOnExceptionStream #-}
235catOnExceptionStream :: Handle -> Handle -> IO ()
236catOnExceptionStream devNull inh =
237 let readEx = S.onException (hClose inh) (S.unfold FH.read inh)
238 in S.fold (FH.write devNull) $ readEx
240-- | Send the file contents to /dev/null with exception handling
241{-# INLINE catFinally #-}
242catFinally :: Handle -> Handle -> IO ()
243catFinally devNull inh =
244 let readEx = IUF.finally (\_ -> hClose inh) FH.read
245 in S.fold (FH.write devNull) $ S.unfold readEx inh
247#ifdef INSPECTION
248inspect $ hasNoTypeClasses 'catFinally
249-- inspect $ 'catFinally `hasNoType` ''Step
250-- inspect $ 'catFinally `hasNoType` ''AT.FlattenState
251-- inspect $ 'catFinally `hasNoType` ''D.ConcatMapUState
252#endif
254-- | Send the file contents to /dev/null with exception handling
255{-# INLINE catFinallyStream #-}
256catFinallyStream :: Handle -> Handle -> IO ()
257catFinallyStream devNull inh =
258 let readEx = S.finally (hClose inh) (S.unfold FH.read inh)
259 in S.fold (FH.write devNull) readEx
261-- | Send the file contents to /dev/null with exception handling
262{-# INLINE catHandle #-}
263catHandle :: Handle -> Handle -> IO ()
264catHandle devNull inh =
265 let handler (_e :: SomeException) = hClose inh >> return 10
266 readEx = IUF.handle (IUF.singleton handler) FH.read
267 in S.fold (FH.write devNull) $ S.unfold readEx inh
269#ifdef INSPECTION
270inspect $ hasNoTypeClasses 'catHandle
271-- inspect $ 'catHandle `hasNoType` ''Step
272-- inspect $ 'catHandle `hasNoType` ''AT.FlattenState
273-- inspect $ 'catHandle `hasNoType` ''D.ConcatMapUState
274#endif
276-- | Send the file contents to /dev/null with exception handling
277{-# INLINE catHandleStream #-}
278catHandleStream :: Handle -> Handle -> IO ()
279catHandleStream devNull inh =
280 let handler (_e :: SomeException) = S.yieldM (hClose inh >> return 10)
281 readEx = S.handle handler (S.unfold FH.read inh)
282 in S.fold (FH.write devNull) $ readEx
284-- | Copy file
285{-# INLINE copy #-}
286copy :: Handle -> Handle -> IO ()
287copy inh outh = S.fold (FH.write outh) (S.unfold FH.read inh)
289#ifdef INSPECTION
290inspect $ hasNoTypeClasses 'copy
291inspect $ 'copy `hasNoType` ''Step
292inspect $ 'copy `hasNoType` ''AT.FlattenState
293inspect $ 'copy `hasNoType` ''D.ConcatMapUState
294#endif
296{-# INLINE readWord8 #-}
297readWord8 :: Handle -> IO ()
298readWord8 inh = S.drain $ S.unfold FH.read inh
300{-# INLINE decodeChar8 #-}
301decodeChar8 :: Handle -> IO ()
302decodeChar8 inh =
303 S.drain
304 $ SS.decodeChar8
305 $ S.unfold FH.read inh
307-- | Copy file
308{-# INLINE copyCodecChar8 #-}
309copyCodecChar8 :: Handle -> Handle -> IO ()
310copyCodecChar8 inh outh =
311 S.fold (FH.write outh)
312 $ SS.encodeChar8
313 $ SS.decodeChar8
314 $ S.unfold FH.read inh
316#ifdef INSPECTION
317inspect $ hasNoTypeClasses 'copyCodecChar8
318inspect $ 'copyCodecChar8 `hasNoType` ''Step
319inspect $ 'copyCodecChar8 `hasNoType` ''AT.FlattenState
320inspect $ 'copyCodecChar8 `hasNoType` ''D.ConcatMapUState
321#endif
323{-# INLINE decodeUtf8Lenient #-}
324decodeUtf8Lenient :: Handle -> IO ()
325decodeUtf8Lenient inh =
326 S.drain
327 $ SS.decodeUtf8Lenient
328 $ S.unfold FH.read inh
330#ifdef INSPECTION
331inspect $ hasNoTypeClasses 'decodeUtf8Lenient
332-- inspect $ 'decodeUtf8Lenient `hasNoType` ''Step
333-- inspect $ 'decodeUtf8Lenient `hasNoType` ''AT.FlattenState
334-- inspect $ 'decodeUtf8Lenient `hasNoType` ''D.ConcatMapUState
335#endif
337-- | Copy file
338{-# INLINE copyCodecUtf8 #-}
339copyCodecUtf8 :: Handle -> Handle -> IO ()
340copyCodecUtf8 inh outh =
341 S.fold (FH.write outh)
342 $ SS.encodeUtf8
343 $ SS.decodeUtf8
344 $ S.unfold FH.read inh
346#ifdef INSPECTION
347inspect $ hasNoTypeClasses 'copyCodecUtf8
348-- inspect $ 'copyCodecUtf8 `hasNoType` ''Step
349-- inspect $ 'copyCodecUtf8 `hasNoType` ''AT.FlattenState
350-- inspect $ 'copyCodecUtf8 `hasNoType` ''D.ConcatMapUState
351#endif
353-- | Copy file
354{-# INLINE copyCodecUtf8Lenient #-}
355copyCodecUtf8Lenient :: Handle -> Handle -> IO ()
356copyCodecUtf8Lenient inh outh =
357 S.fold (FH.write outh)
358 $ SS.encodeUtf8
359 $ SS.decodeUtf8Lenient
360 $ S.unfold FH.read inh
362#ifdef INSPECTION
363inspect $ hasNoTypeClasses 'copyCodecUtf8Lenient
364-- inspect $ 'copyCodecUtf8Lenient `hasNoType` ''Step
365-- inspect $ 'copyCodecUtf8Lenient `hasNoType` ''AT.FlattenState
366-- inspect $ 'copyCodecUtf8Lenient `hasNoType` ''D.ConcatMapUState
367#endif
369-- | Slice in chunks of size n and get the count of chunks.
370{-# INLINE chunksOf #-}
371chunksOf :: Int -> Handle -> IO Int
372chunksOf n inh =
373 -- writeNUnsafe gives 2.5x boost here over writeN.
374 S.length $ S.chunksOf n (AT.writeNUnsafe n) (S.unfold FH.read inh)
376#ifdef INSPECTION
377inspect $ hasNoTypeClasses 'chunksOf
378inspect $ 'chunksOf `hasNoType` ''Step
379inspect $ 'chunksOf `hasNoType` ''AT.FlattenState
380inspect $ 'chunksOf `hasNoType` ''D.ConcatMapUState
381inspect $ 'chunksOf `hasNoType` ''GroupState
382#endif
384-- This is to make sure that the concatMap in FH.read, groupsOf and foldlM'
385-- together can fuse.
386--
387-- | Slice in chunks of size n and get the count of chunks.
388{-# INLINE chunksOfD #-}
389chunksOfD :: Int -> Handle -> IO Int
390chunksOfD n inh =
391 D.foldlM' (\i _ -> return $ i + 1) 0
392 $ D.groupsOf n (AT.writeNUnsafe n)
393 $ D.fromStreamK (S.unfold FH.read inh)
395#ifdef INSPECTION
396inspect $ hasNoTypeClasses 'chunksOf
397inspect $ 'chunksOf `hasNoType` ''Step
398inspect $ 'chunksOfD `hasNoType` ''GroupState
399inspect $ 'chunksOfD `hasNoType` ''AT.FlattenState
400inspect $ 'chunksOfD `hasNoType` ''D.ConcatMapUState
401#endif
403-- XXX splitSuffixOn requires -funfolding-use-threshold=150 for better fusion
404-- | Lines and unlines
405{-# INLINE linesUnlinesCopy #-}
406linesUnlinesCopy :: Handle -> Handle -> IO ()
407linesUnlinesCopy inh outh =
408 S.fold (FH.write outh)
409 $ SS.encodeChar8
410 $ SS.unlines
411 $ SS.lines
412 $ SS.decodeChar8
413 $ S.unfold FH.read inh
415#ifdef INSPECTION
416inspect $ hasNoTypeClassesExcept 'linesUnlinesCopy [''Storable]
417-- inspect $ 'linesUnlinesCopy `hasNoType` ''Step
418-- inspect $ 'linesUnlinesCopy `hasNoType` ''AT.FlattenState
419-- inspect $ 'linesUnlinesCopy `hasNoType` ''D.ConcatMapUState
420#endif
422foreign import ccall unsafe "u_iswspace"
423 iswspace :: Int -> Int
425-- Code copied from base/Data.Char to INLINE it
426{-# INLINE isSpace #-}
427isSpace :: Char -> Bool
428-- isSpace includes non-breaking space
429-- The magic 0x377 isn't really that magical. As of 2014, all the codepoints
430-- at or below 0x377 have been assigned, so we shouldn't have to worry about
431-- any new spaces appearing below there. It would probably be best to
432-- use branchless ||, but currently the eqLit transformation will undo that,
433-- so we'll do it like this until there's a way around that.
434isSpace c
435 | uc <= 0x377 = uc == 32 || uc - 0x9 <= 4 || uc == 0xa0
436 | otherwise = iswspace (ord c) /= 0
437 where
438 uc = fromIntegral (ord c) :: Word
440{-# INLINE isSp #-}
441isSp :: Word8 -> Bool
442isSp = isSpace . chr . fromIntegral
444-- | Word, unwords and copy
445{-# INLINE wordsUnwordsCopyWord8 #-}
446wordsUnwordsCopyWord8 :: Handle -> Handle -> IO ()
447wordsUnwordsCopyWord8 inh outh =
448 S.fold (FH.write outh)
449 $ S.concatUnfold IUF.fromList
450 $ S.intersperse [32]
451 $ S.wordsBy isSp FL.toList
452 $ S.unfold FH.read inh
454#ifdef INSPECTION
455inspect $ hasNoTypeClasses 'wordsUnwordsCopyWord8
456-- inspect $ 'wordsUnwordsCopyWord8 `hasNoType` ''Step
457-- inspect $ 'wordsUnwordsCopyWord8 `hasNoType` ''D.ConcatMapUState
458#endif
460-- | Word, unwords and copy
461{-# INLINE wordsUnwordsCopy #-}
462wordsUnwordsCopy :: Handle -> Handle -> IO ()
463wordsUnwordsCopy inh outh =
464 S.fold (FH.write outh)
465 $ SS.encodeChar8
466 $ S.concatUnfold IUF.fromList
467 $ S.intersperse " "
468 -- Array allocation is too expensive for such small strings. So just use
469 -- lists instead.
470 --
471 -- -- $ SS.unwords
472 -- -- $ SS.words
473 --
474 -- XXX This pipeline does not fuse with wordsBy but fuses with splitOn
475 -- with -funfolding-use-threshold=300. With wordsBy it does not fuse
476 -- even with high limits for inlining and spec-constr ghc options. With
477 -- -funfolding-use-threshold=400 it performs pretty well and there
478 -- is no evidence in the core that a join point involving Step
479 -- constructors is not getting inlined. Not being able to fuse at all in
480 -- this case could be an unknown issue, need more investigation.
481 $ S.wordsBy isSpace FL.toList
482 -- -- $ S.splitOn isSpace FL.toList
483 $ SS.decodeChar8
484 $ S.unfold FH.read inh
486#ifdef INSPECTION
487-- inspect $ hasNoTypeClasses 'wordsUnwordsCopy
488-- inspect $ 'wordsUnwordsCopy `hasNoType` ''Step
489-- inspect $ 'wordsUnwordsCopy `hasNoType` ''AT.FlattenState
490-- inspect $ 'wordsUnwordsCopy `hasNoType` ''D.ConcatMapUState
491#endif
493lf :: Word8
494lf = fromIntegral (ord '\n')
496toarr :: String -> A.Array Word8
497toarr = A.fromList . map (fromIntegral . ord)
499-- | Split on line feed.
500{-# INLINE splitOn #-}
501splitOn :: Handle -> IO Int
502splitOn inh =
503 (S.length $ S.splitOn (== lf) FL.drain
504 $ S.unfold FH.read inh) -- >>= print
506#ifdef INSPECTION
507inspect $ hasNoTypeClasses 'splitOn
508inspect $ 'splitOn `hasNoType` ''Step
509inspect $ 'splitOn `hasNoType` ''AT.FlattenState
510inspect $ 'splitOn `hasNoType` ''D.ConcatMapUState
511#endif
513-- | Split suffix on line feed.
514{-# INLINE splitOnSuffix #-}
515splitOnSuffix :: Handle -> IO Int
516splitOnSuffix inh =
517 (S.length $ S.splitOnSuffix (== lf) FL.drain
518 $ S.unfold FH.read inh) -- >>= print
520#ifdef INSPECTION
521inspect $ hasNoTypeClasses 'splitOnSuffix
522inspect $ 'splitOnSuffix `hasNoType` ''Step
523inspect $ 'splitOnSuffix `hasNoType` ''AT.FlattenState
524inspect $ 'splitOnSuffix `hasNoType` ''D.ConcatMapUState
525#endif
527-- | Words by space
528{-# INLINE wordsBy #-}
529wordsBy :: Handle -> IO Int
530wordsBy inh =
531 (S.length $ S.wordsBy isSp FL.drain
532 $ S.unfold FH.read inh) -- >>= print
534#ifdef INSPECTION
535inspect $ hasNoTypeClasses 'wordsBy
536inspect $ 'wordsBy `hasNoType` ''Step
537inspect $ 'wordsBy `hasNoType` ''AT.FlattenState
538inspect $ 'wordsBy `hasNoType` ''D.ConcatMapUState
539#endif
541-- | Split on a word8 sequence.
542{-# INLINE splitOnSeq #-}
543splitOnSeq :: String -> Handle -> IO Int
544splitOnSeq str inh =
545 (S.length $ IP.splitOnSeq (toarr str) FL.drain
546 $ S.unfold FH.read inh) -- >>= print
548#ifdef INSPECTION
549inspect $ hasNoTypeClasses 'splitOnSeq
550-- inspect $ 'splitOnSeq `hasNoType` ''Step
551-- inspect $ 'splitOnSeq `hasNoType` ''AT.FlattenState
552-- inspect $ 'splitOnSeq `hasNoType` ''D.ConcatMapUState
553#endif
555-- | Split on a character sequence.
556{-# INLINE splitOnSeqUtf8 #-}
557splitOnSeqUtf8 :: String -> Handle -> IO Int
558splitOnSeqUtf8 str inh =
559 (S.length $ IP.splitOnSeq (A.fromList str) FL.drain
560 $ SS.decodeUtf8ArraysLenient
561 $ IFH.toStreamArrays inh) -- >>= print
563-- | Split on suffix sequence.
564{-# INLINE splitOnSuffixSeq #-}
565splitOnSuffixSeq :: String -> Handle -> IO Int
566splitOnSuffixSeq str inh =
567 (S.length $ IP.splitOnSuffixSeq (toarr str) FL.drain
568 $ S.unfold FH.read inh) -- >>= print
570#ifdef INSPECTION
571inspect $ hasNoTypeClasses 'splitOnSuffixSeq
572-- inspect $ 'splitOnSuffixSeq `hasNoType` ''Step
573-- inspect $ 'splitOnSuffixSeq `hasNoType` ''AT.FlattenState
574-- inspect $ 'splitOnSuffixSeq `hasNoType` ''D.ConcatMapUState
575#endif