2-- Module : Streamly.Benchmark.FileIO.Stream
3-- Copyright : (c) 2019 Composewell Technologies
6-- Maintainer : harendra.kumar@gmail.com
7-- Stability : experimental
12#ifdef __HADDOCK_VERSION__
17{-# LANGUAGE TemplateHaskell #-}
18{-# OPTIONS_GHC -fplugin Test.Inspection.Plugin #-}
21module Streamly.Benchmark.FileIO.Stream
34 , wordsUnwordsCopyWord8
41 , copyCodecUtf8Lenient
53import Data.Char (ord, chr)
54import Data.Word (Word8)
55import System.IO (Handle)
56import Prelude hiding (last, length)
58import qualified Streamly.FileSystem.Handle as FH
59import qualified Streamly.FileSystem.Handle.Internal as FH
60import qualified Streamly.Memory.Array as A
61import qualified Streamly.Memory.Array.Types as AT
62import qualified Streamly.Prelude.Internal as S
63import qualified Streamly.Fold as FL
64import qualified Streamly.Data.String as SS
65import qualified Streamly.Internal as Internal
66import qualified Streamly.Streams.StreamD as D
69import Foreign.Storable (Storable)
70import Streamly.Streams.StreamD.Type (Step(..), GroupState)
74-- | Get the last byte from a file bytestream.
76last :: Handle -> IO (Maybe Word8)
77last = S.last . FH.read
80inspect $ hasNoTypeClasses 'last
81inspect $ 'last `hasNoType` ''Step
82inspect $ 'last `hasNoType` ''AT.FlattenState
83inspect $ 'last `hasNoType` ''D.ConcatMapUState
86-- assert that flattenArrays constructors are not present
87-- | Count the number of bytes in a file.
88{-# INLINE countBytes #-}
89countBytes :: Handle -> IO Int
90countBytes = S.length . FH.read
93inspect $ hasNoTypeClasses 'countBytes
94inspect $ 'countBytes `hasNoType` ''Step
95inspect $ 'countBytes `hasNoType` ''AT.FlattenState
96inspect $ 'countBytes `hasNoType` ''D.ConcatMapUState
99-- | Count the number of lines in a file.
100{-# INLINE countLines #-}
101countLines :: Handle -> IO Int
104 . SS.foldLines FL.drain
109inspect $ hasNoTypeClasses 'countLines
110inspect $ 'countLines `hasNoType` ''Step
111inspect $ 'countLines `hasNoType` ''AT.FlattenState
112inspect $ 'countLines `hasNoType` ''D.ConcatMapUState
115-- | Count the number of words in a file.
116{-# INLINE countWords #-}
117countWords :: Handle -> IO Int
120 . SS.foldWords FL.drain
125inspect $ hasNoTypeClasses 'countWords
126-- inspect $ 'countWords `hasNoType` ''Step
127-- inspect $ 'countWords `hasNoType` ''D.ConcatMapUState
130-- | Count the number of lines in a file.
131{-# INLINE countLinesU #-}
132countLinesU :: Handle -> IO Int
135 $ SS.foldLines FL.drain
137 $ Internal.concatMapU Internal.readU (FH.readArrays inh)
140inspect $ hasNoTypeClasses 'countLinesU
141inspect $ 'countLinesU `hasNoType` ''Step
142inspect $ 'countLinesU `hasNoType` ''D.ConcatMapUState
145-- | Sum the bytes in a file.
146{-# INLINE sumBytes #-}
147sumBytes :: Handle -> IO Word8
148sumBytes = S.sum . FH.read
151inspect $ hasNoTypeClasses 'sumBytes
152inspect $ 'sumBytes `hasNoType` ''Step
153inspect $ 'sumBytes `hasNoType` ''AT.FlattenState
154inspect $ 'sumBytes `hasNoType` ''D.ConcatMapUState
157-- | Send the file contents to /dev/null
159cat :: Handle -> Handle -> IO ()
160cat devNull inh = S.runFold (FH.write devNull) $ FH.read inh
163inspect $ hasNoTypeClasses 'cat
164inspect $ 'cat `hasNoType` ''Step
165inspect $ 'cat `hasNoType` ''AT.FlattenState
166inspect $ 'cat `hasNoType` ''D.ConcatMapUState
169-- | Send the file contents to /dev/null
170{-# INLINE catStreamWrite #-}
171catStreamWrite :: Handle -> Handle -> IO ()
172catStreamWrite devNull inh = FH.writeS devNull $ FH.read inh
175inspect $ hasNoTypeClasses 'catStreamWrite
176inspect $ 'catStreamWrite `hasNoType` ''Step
177inspect $ 'catStreamWrite `hasNoType` ''AT.FlattenState
178inspect $ 'catStreamWrite `hasNoType` ''D.ConcatMapUState
183copy :: Handle -> Handle -> IO ()
184copy inh outh = S.runFold (FH.write outh) (FH.read inh)
187inspect $ hasNoTypeClasses 'copy
188inspect $ 'copy `hasNoType` ''Step
189inspect $ 'copy `hasNoType` ''AT.FlattenState
190inspect $ 'copy `hasNoType` ''D.ConcatMapUState
193{-# INLINE readWord8 #-}
194readWord8 :: Handle -> IO ()
195readWord8 inh = S.drain $ FH.read inh
197{-# INLINE decodeChar8 #-}
198decodeChar8 :: Handle -> IO ()
205{-# INLINE copyCodecChar8 #-}
206copyCodecChar8 :: Handle -> Handle -> IO ()
207copyCodecChar8 inh outh =
208 S.runFold (FH.write outh)
214inspect $ hasNoTypeClasses 'copyCodecChar8
215inspect $ 'copyCodecChar8 `hasNoType` ''Step
216inspect $ 'copyCodecChar8 `hasNoType` ''AT.FlattenState
217inspect $ 'copyCodecChar8 `hasNoType` ''D.ConcatMapUState
220{-# INLINE decodeUtf8Lenient #-}
221decodeUtf8Lenient :: Handle -> IO ()
222decodeUtf8Lenient inh =
224 $ SS.decodeUtf8Lenient
228inspect $ hasNoTypeClasses 'decodeUtf8Lenient
229-- inspect $ 'decodeUtf8Lenient `hasNoType` ''Step
230-- inspect $ 'decodeUtf8Lenient `hasNoType` ''AT.FlattenState
231-- inspect $ 'decodeUtf8Lenient `hasNoType` ''D.ConcatMapUState
235{-# INLINE copyCodecUtf8 #-}
236copyCodecUtf8 :: Handle -> Handle -> IO ()
237copyCodecUtf8 inh outh =
238 S.runFold (FH.write outh)
244inspect $ hasNoTypeClasses 'copyCodecUtf8
245-- inspect $ 'copyCodecUtf8 `hasNoType` ''Step
246-- inspect $ 'copyCodecUtf8 `hasNoType` ''AT.FlattenState
247-- inspect $ 'copyCodecUtf8 `hasNoType` ''D.ConcatMapUState
251{-# INLINE copyCodecUtf8Lenient #-}
252copyCodecUtf8Lenient :: Handle -> Handle -> IO ()
253copyCodecUtf8Lenient inh outh =
254 S.runFold (FH.write outh)
256 $ SS.decodeUtf8Lenient
260inspect $ hasNoTypeClasses 'copyCodecUtf8Lenient
261-- inspect $ 'copyCodecUtf8Lenient `hasNoType` ''Step
262-- inspect $ 'copyCodecUtf8Lenient `hasNoType` ''AT.FlattenState
263-- inspect $ 'copyCodecUtf8Lenient `hasNoType` ''D.ConcatMapUState
266-- | Slice in chunks of size n and get the count of chunks.
267{-# INLINE chunksOf #-}
268chunksOf :: Int -> Handle -> IO Int
270 -- writeNUnsafe gives 2.5x boost here over writeN.
271 S.length $ S.chunksOf n (AT.writeNUnsafe n) (FH.read inh)
274inspect $ hasNoTypeClasses 'chunksOf
275inspect $ 'chunksOf `hasNoType` ''Step
276inspect $ 'chunksOf `hasNoType` ''AT.FlattenState
277inspect $ 'chunksOf `hasNoType` ''D.ConcatMapUState
278inspect $ 'chunksOf `hasNoType` ''GroupState
281-- This is to make sure that the concatMap in FH.read, groupsOf and foldlM'
284-- | Slice in chunks of size n and get the count of chunks.
285{-# INLINE chunksOfD #-}
286chunksOfD :: Int -> Handle -> IO Int
288 D.foldlM' (\i _ -> return $ i + 1) 0
289 $ D.groupsOf n (AT.writeNUnsafe n)
290 $ D.fromStreamK (FH.read inh)
293inspect $ hasNoTypeClasses 'chunksOf
294inspect $ 'chunksOf `hasNoType` ''Step
295inspect $ 'chunksOfD `hasNoType` ''GroupState
296inspect $ 'chunksOfD `hasNoType` ''AT.FlattenState
297inspect $ 'chunksOfD `hasNoType` ''D.ConcatMapUState
300-- XXX splitSuffixOn requires -funfolding-use-threshold=150 for better fusion
301-- | Lines and unlines
302{-# INLINE linesUnlinesCopy #-}
303linesUnlinesCopy :: Handle -> Handle -> IO ()
304linesUnlinesCopy inh outh =
305 S.runFold (FH.write outh)
313inspect $ hasNoTypeClassesExcept 'linesUnlinesCopy [''Storable]
314-- inspect $ 'linesUnlinesCopy `hasNoType` ''Step
315-- inspect $ 'linesUnlinesCopy `hasNoType` ''AT.FlattenState
316-- inspect $ 'linesUnlinesCopy `hasNoType` ''D.ConcatMapUState
319foreign import ccall unsafe "u_iswspace"
320 iswspace :: Int -> Int
322-- Code copied from base/Data.Char to INLINE it
323{-# INLINE isSpace #-}
324isSpace :: Char -> Bool
325-- isSpace includes non-breaking space
326-- The magic 0x377 isn't really that magical. As of 2014, all the codepoints
327-- at or below 0x377 have been assigned, so we shouldn't have to worry about
328-- any new spaces appearing below there. It would probably be best to
329-- use branchless ||, but currently the eqLit transformation will undo that,
330-- so we'll do it like this until there's a way around that.
332 | uc <= 0x377 = uc == 32 || uc - 0x9 <= 4 || uc == 0xa0
333 | otherwise = iswspace (ord c) /= 0
335 uc = fromIntegral (ord c) :: Word
339isSp = isSpace . chr . fromIntegral
341-- | Word, unwords and copy
342{-# INLINE wordsUnwordsCopyWord8 #-}
343wordsUnwordsCopyWord8 :: Handle -> Handle -> IO ()
344wordsUnwordsCopyWord8 inh outh =
345 S.runFold (FH.write outh)
346 $ Internal.concatMapU Internal.fromList
348 $ S.wordsBy isSp FL.toList
352inspect $ hasNoTypeClasses 'wordsUnwordsCopyWord8
353-- inspect $ 'wordsUnwordsCopyWord8 `hasNoType` ''Step
354-- inspect $ 'wordsUnwordsCopyWord8 `hasNoType` ''D.ConcatMapUState
357-- | Word, unwords and copy
358{-# INLINE wordsUnwordsCopy #-}
359wordsUnwordsCopy :: Handle -> Handle -> IO ()
360wordsUnwordsCopy inh outh =
361 S.runFold (FH.write outh)
363 $ Internal.concatMapU Internal.fromList
365 -- Array allocation is too expensive for such small strings. So just use
371 -- XXX This pipeline does not fuse with wordsBy but fuses with splitOn
372 -- with -funfolding-use-threshold=300. With wordsBy it does not fuse
373 -- even with high limits for inlining and spec-constr ghc options. With
374 -- -funfolding-use-threshold=400 it performs pretty well and there
375 -- is no evidence in the core that a join point involving Step
376 -- constructors is not getting inlined. Not being able to fuse at all in
377 -- this case could be an unknown issue, need more investigation.
378 $ S.wordsBy isSpace FL.toList
379 -- -- $ S.splitOn isSpace FL.toList
384-- inspect $ hasNoTypeClasses 'wordsUnwordsCopy
385-- inspect $ 'wordsUnwordsCopy `hasNoType` ''Step
386-- inspect $ 'wordsUnwordsCopy `hasNoType` ''AT.FlattenState
387-- inspect $ 'wordsUnwordsCopy `hasNoType` ''D.ConcatMapUState
391lf = fromIntegral (ord '\n')
393toarr :: String -> A.Array Word8
394toarr = A.fromList . map (fromIntegral . ord)
396-- | Split on line feed.
397{-# INLINE splitOn #-}
398splitOn :: Handle -> IO Int
400 (S.length $ S.splitOn (== lf) FL.drain
401 $ FH.read inh) -- >>= print
404inspect $ hasNoTypeClasses 'splitOn
405inspect $ 'splitOn `hasNoType` ''Step
406inspect $ 'splitOn `hasNoType` ''AT.FlattenState
407inspect $ 'splitOn `hasNoType` ''D.ConcatMapUState
410-- | Split suffix on line feed.
411{-# INLINE splitOnSuffix #-}
412splitOnSuffix :: Handle -> IO Int
414 (S.length $ S.splitOnSuffix (== lf) FL.drain
415 $ FH.read inh) -- >>= print
418inspect $ hasNoTypeClasses 'splitOnSuffix
419inspect $ 'splitOnSuffix `hasNoType` ''Step
420inspect $ 'splitOnSuffix `hasNoType` ''AT.FlattenState
421inspect $ 'splitOnSuffix `hasNoType` ''D.ConcatMapUState
425{-# INLINE wordsBy #-}
426wordsBy :: Handle -> IO Int
428 (S.length $ S.wordsBy isSp FL.drain
429 $ FH.read inh) -- >>= print
432inspect $ hasNoTypeClasses 'wordsBy
433inspect $ 'wordsBy `hasNoType` ''Step
434inspect $ 'wordsBy `hasNoType` ''AT.FlattenState
435inspect $ 'wordsBy `hasNoType` ''D.ConcatMapUState
438-- | Split on a word8 sequence.
439{-# INLINE splitOnSeq #-}
440splitOnSeq :: String -> Handle -> IO Int
442 (S.length $ Internal.splitOnSeq (toarr str) FL.drain
443 $ FH.read inh) -- >>= print
446inspect $ hasNoTypeClasses 'splitOnSeq
447-- inspect $ 'splitOnSeq `hasNoType` ''Step
448-- inspect $ 'splitOnSeq `hasNoType` ''AT.FlattenState
449-- inspect $ 'splitOnSeq `hasNoType` ''D.ConcatMapUState
452-- | Split on a character sequence.
453{-# INLINE splitOnSeqUtf8 #-}
454splitOnSeqUtf8 :: String -> Handle -> IO Int
455splitOnSeqUtf8 str inh =
456 (S.length $ Internal.splitOnSeq (A.fromList str) FL.drain
457 $ SS.decodeUtf8ArraysLenient
458 $ FH.readArrays inh) -- >>= print
460-- | Split on suffix sequence.
461{-# INLINE splitOnSuffixSeq #-}
462splitOnSuffixSeq :: String -> Handle -> IO Int
463splitOnSuffixSeq str inh =
464 (S.length $ Internal.splitOnSuffixSeq (toarr str) FL.drain
465 $ FH.read inh) -- >>= print
468inspect $ hasNoTypeClasses 'splitOnSuffixSeq
469-- inspect $ 'splitOnSuffixSeq `hasNoType` ''Step
470-- inspect $ 'splitOnSuffixSeq `hasNoType` ''AT.FlattenState
471-- inspect $ 'splitOnSuffixSeq `hasNoType` ''D.ConcatMapUState
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