@@ -0,0 +1,308 @@
1+-- |
2+-- Module : Streamly.Unicode.Stream
3+-- Copyright : (c) 2019 Composewell Technologies
4+--
5+-- License : BSD3
6+-- Maintainer : streamly@composewell.com
7+-- Stability : experimental
8+-- Portability : GHC
9+
10+{-# LANGUAGE CPP #-}
11+{-# LANGUAGE ScopedTypeVariables #-}
12+
13+#ifdef __HADDOCK_VERSION__
14+#undef INSPECTION
15+#endif
16+
17+#ifdef INSPECTION
18+{-# LANGUAGE TemplateHaskell #-}
19+{-# OPTIONS_GHC -fplugin Test.Inspection.Plugin #-}
20+#endif
21+
22+import Control.Exception (SomeException)
23+import System.IO (Handle, hClose, hPutChar)
24+import Prelude hiding (last, length)
25+import Streamly.Internal.Data.Array.Storable.Foreign.Types (defaultChunkSize)
26+
27+import qualified Streamly.Data.Fold as FL
28+import qualified Streamly.Unicode.Stream as SS
29+import qualified Streamly.FileSystem.Handle as FH
30+import qualified Streamly.Internal.Data.Unfold as IUF
31+import qualified Streamly.Internal.Unicode.Stream as IUS
32+import qualified Streamly.Internal.FileSystem.Handle as IFH
33+import qualified Streamly.Internal.Unicode.Array.Char as IUA
34+import qualified Streamly.Internal.Data.Stream.IsStream as IP
35+import qualified Streamly.Data.Array.Storable.Foreign as A
36+import qualified Streamly.Prelude as S
37+
38+import Gauge hiding (env)
39+import Streamly.Benchmark.CommonH
40+import Streamly.Benchmark.Common
41+
42+#ifdef INSPECTION
43+import Foreign.Storable (Storable)
44+import Streamly.Internal.Data.Stream.StreamD.Type (Step(..))
45+import qualified Streamly.Internal.Data.Stream.StreamD.Type as D
46+import qualified Streamly.Internal.Data.Tuple.Strict as Strict
47+import qualified Streamly.Internal.Data.Array.Storable.Foreign as A
48+import qualified Streamly.Internal.Data.Array.Storable.Foreign.Types as AT
49+
50+import Test.Inspection
51+#endif
52+
53+moduleName :: String
54+moduleName = "Unicode.Stream"
55+
56+-- | Copy file
57+copyCodecUtf8ArraysLenient :: Handle -> Handle -> IO ()
58+copyCodecUtf8ArraysLenient inh outh =
59+ S.fold (FH.write outh)
60+ $ SS.encodeUtf8'
61+ $ IUS.decodeUtf8Arrays
62+ $ IFH.toChunks inh
63+
64+#ifdef INSPECTION
65+inspect $ hasNoTypeClasses 'copyCodecUtf8ArraysLenient
66+-- inspect $ 'copyCodecUtf8ArraysLenient `hasNoType` ''Step
67+#endif
68+
69+o_1_space_copy_chunked :: BenchEnv -> [Benchmark]
70+o_1_space_copy_chunked env =
71+ [ bgroup "copy/toChunks"
72+ [
73+ mkBenchSmall "decodeEncodeUtf8Lenient" env $ \inH outH ->
74+ copyCodecUtf8ArraysLenient inH outH
75+ ]
76+ ]
77+
78+-------------------------------------------------------------------------------
79+-- copy with group/ungroup transformations
80+-------------------------------------------------------------------------------
81+
82+linesUnlinesCopy :: Handle -> Handle -> IO ()
83+linesUnlinesCopy inh outh =
84+ S.fold (FH.write outh)
85+ $ SS.encodeLatin1'
86+ $ IUS.unlines IUF.fromList
87+ $ S.splitOnSuffix (== '\n') FL.toList
88+ $ SS.decodeLatin1
89+ $ S.unfold FH.read inh
90+
91+linesUnlinesArrayWord8Copy :: Handle -> Handle -> IO ()
92+linesUnlinesArrayWord8Copy inh outh =
93+ S.fold (FH.write outh)
94+ $ IP.interposeSuffix 10 A.read
95+ $ S.splitOnSuffix (== 10) A.write
96+ $ S.unfold FH.read inh
97+
98+-- XXX splitSuffixOn requires -funfolding-use-threshold=150 for better fusion
99+-- | Lines and unlines
100+linesUnlinesArrayCharCopy :: Handle -> Handle -> IO ()
101+linesUnlinesArrayCharCopy inh outh =
102+ S.fold (FH.write outh)
103+ $ SS.encodeLatin1'
104+ $ IUA.unlines
105+ $ IUA.lines
106+ $ SS.decodeLatin1
107+ $ S.unfold FH.read inh
108+
109+#ifdef INSPECTION
110+inspect $ hasNoTypeClassesExcept 'linesUnlinesArrayCharCopy [''Storable]
111+-- inspect $ 'linesUnlinesArrayCharCopy `hasNoType` ''Step
112+#endif
113+
114+-- XXX to write this we need to be able to map decodeUtf8 on the A.read fold.
115+-- For that we have to write decodeUtf8 as a Pipe.
116+{-
117+{-# INLINE linesUnlinesArrayUtf8Copy #-}
118+linesUnlinesArrayUtf8Copy :: Handle -> Handle -> IO ()
119+linesUnlinesArrayUtf8Copy inh outh =
120+ S.fold (FH.write outh)
121+ $ SS.encodeLatin1'
122+ $ IP.intercalate (A.fromList [10]) (pipe SS.decodeUtf8P A.read)
123+ $ S.splitOnSuffix (== '\n') (IFL.lmap SS.encodeUtf8' A.write)
124+ $ SS.decodeLatin1
125+ $ S.unfold FH.read inh
126+-}
127+
128+-- | Word, unwords and copy
129+wordsUnwordsCopyWord8 :: Handle -> Handle -> IO ()
130+wordsUnwordsCopyWord8 inh outh =
131+ S.fold (FH.write outh)
132+ $ IP.interposeSuffix 32 IUF.fromList
133+ $ S.wordsBy isSp FL.toList
134+ $ S.unfold FH.read inh
135+
136+#ifdef INSPECTION
137+inspect $ hasNoTypeClasses 'wordsUnwordsCopyWord8
138+-- inspect $ 'wordsUnwordsCopyWord8 `hasNoType` ''Step
139+#endif
140+
141+-- | Word, unwords and copy
142+wordsUnwordsCopy :: Handle -> Handle -> IO ()
143+wordsUnwordsCopy inh outh =
144+ S.fold (FH.write outh)
145+ $ SS.encodeLatin1'
146+ $ IUS.unwords IUF.fromList
147+ -- XXX This pipeline does not fuse with wordsBy but fuses with splitOn
148+ -- with -funfolding-use-threshold=300. With wordsBy it does not fuse
149+ -- even with high limits for inlining and spec-constr ghc options. With
150+ -- -funfolding-use-threshold=400 it performs pretty well and there
151+ -- is no evidence in the core that a join point involving Step
152+ -- constructors is not getting inlined. Not being able to fuse at all in
153+ -- this case could be an unknown issue, need more investigation.
154+ $ S.wordsBy isSpace FL.toList
155+ -- -- $ S.splitOn isSpace FL.toList
156+ $ SS.decodeLatin1
157+ $ S.unfold FH.read inh
158+
159+#ifdef INSPECTION
160+-- inspect $ hasNoTypeClasses 'wordsUnwordsCopy
161+-- inspect $ 'wordsUnwordsCopy `hasNoType` ''Step
162+#endif
163+
164+wordsUnwordsCharArrayCopy :: Handle -> Handle -> IO ()
165+wordsUnwordsCharArrayCopy inh outh =
166+ S.fold (FH.write outh)
167+ $ SS.encodeLatin1'
168+ $ IUA.unwords
169+ $ IUA.words
170+ $ SS.decodeLatin1
171+ $ S.unfold FH.read inh
172+
173+o_1_space_copy_read_group_ungroup :: BenchEnv -> [Benchmark]
174+o_1_space_copy_read_group_ungroup env =
175+ [ bgroup "copy/read/group-ungroup"
176+ [ mkBenchSmall "US.unlines . S.splitOnSuffix ([Word8])" env
177+ $ \inh outh -> linesUnlinesCopy inh outh
178+ , mkBenchSmall "S.interposeSuffix . S.splitOnSuffix(Array Word8)" env
179+ $ \inh outh -> linesUnlinesArrayWord8Copy inh outh
180+ , mkBenchSmall "UA.unlines . UA.lines (Array Char)" env
181+ $ \inh outh -> linesUnlinesArrayCharCopy inh outh
182+
183+ , mkBenchSmall "S.interposeSuffix . S.wordsBy ([Word8])" env
184+ $ \inh outh -> wordsUnwordsCopyWord8 inh outh
185+ , mkBenchSmall "US.unwords . S.wordsBy ([Char])" env
186+ $ \inh outh -> wordsUnwordsCopy inh outh
187+ , mkBenchSmall "UA.unwords . UA.words (Array Char)" env
188+ $ \inh outh -> wordsUnwordsCharArrayCopy inh outh
189+ ]
190+ ]
191+
192+-------------------------------------------------------------------------------
193+-- copy unfold
194+-------------------------------------------------------------------------------
195+
196+-- -- | Copy file
197+-- copyStream :: Handle -> Handle -> IO ()
198+-- copyStream inh outh = S.fold (FH.write outh) (S.unfold FH.read inh)
199+
200+-- #ifdef INSPECTION
201+-- inspect $ hasNoTypeClasses 'copyStream
202+-- inspect $ 'copyStream `hasNoType` ''Step -- S.unfold
203+-- inspect $ 'copyStream `hasNoType` ''IUF.ConcatState -- FH.read/UF.concat
204+-- inspect $ 'copyStream `hasNoType` ''A.ReadUState -- FH.read/A.read
205+-- inspect $ 'copyStream `hasNoType` ''AT.ArrayUnsafe -- FH.write/writeNUnsafe
206+-- inspect $ 'copyStream `hasNoType` ''Strict.Tuple3' -- FH.write/lchunksOf
207+-- #endif
208+
209+-- | Copy file (encodeLatin1')
210+copyStreamLatin1' :: Handle -> Handle -> IO ()
211+copyStreamLatin1' inh outh =
212+ S.fold (FH.write outh)
213+ $ SS.encodeLatin1'
214+ $ SS.decodeLatin1
215+ $ S.unfold FH.read inh
216+
217+#ifdef INSPECTION
218+inspect $ hasNoTypeClasses 'copyStreamLatin1'
219+inspect $ 'copyStreamLatin1' `hasNoType` ''Step
220+inspect $ 'copyStreamLatin1' `hasNoType` ''IUF.ConcatState -- FH.read/UF.concat
221+inspect $ 'copyStreamLatin1' `hasNoType` ''A.ReadUState -- FH.read/A.read
222+inspect $ 'copyStreamLatin1' `hasNoType` ''AT.ArrayUnsafe -- FH.write/writeNUnsafe
223+inspect $ 'copyStreamLatin1' `hasNoType` ''Strict.Tuple3' -- FH.write/lchunksOf
224+#endif
225+
226+-- | Copy file (encodeLatin1)
227+copyStreamLatin1 :: Handle -> Handle -> IO ()
228+copyStreamLatin1 inh outh =
229+ S.fold (FH.write outh)
230+ $ SS.encodeLatin1
231+ $ SS.decodeLatin1
232+ $ S.unfold FH.read inh
233+
234+#ifdef INSPECTION
235+inspect $ hasNoTypeClasses 'copyStreamLatin1
236+inspect $ 'copyStreamLatin1 `hasNoType` ''Step
237+inspect $ 'copyStreamLatin1 `hasNoType` ''IUF.ConcatState -- FH.read/UF.concat
238+inspect $ 'copyStreamLatin1 `hasNoType` ''A.ReadUState -- FH.read/A.read
239+inspect $ 'copyStreamLatin1 `hasNoType` ''AT.ArrayUnsafe -- FH.write/writeNUnsafe
240+inspect $ 'copyStreamLatin1 `hasNoType` ''Strict.Tuple3' -- FH.write/lchunksOf
241+#endif
242+
243+-- | Copy file
244+_copyStreamUtf8' :: Handle -> Handle -> IO ()
245+_copyStreamUtf8' inh outh =
246+ S.fold (FH.write outh)
247+ $ SS.encodeUtf8'
248+ $ SS.decodeUtf8'
249+ $ S.unfold FH.read inh
250+
251+#ifdef INSPECTION
252+inspect $ hasNoTypeClasses '_copyStreamUtf8'
253+-- inspect $ '_copyStreamUtf8 `hasNoType` ''Step
254+-- inspect $ '_copyStreamUtf8 `hasNoType` ''AT.FlattenState
255+-- inspect $ '_copyStreamUtf8 `hasNoType` ''D.ConcatMapUState
256+#endif
257+
258+-- | Copy file
259+copyStreamUtf8 :: Handle -> Handle -> IO ()
260+copyStreamUtf8 inh outh =
261+ S.fold (FH.write outh)
262+ $ SS.encodeUtf8
263+ $ SS.decodeUtf8
264+ $ S.unfold FH.read inh
265+
266+#ifdef INSPECTION
267+inspect $ hasNoTypeClasses 'copyStreamUtf8
268+-- inspect $ 'copyStreamUtf8Lax `hasNoType` ''Step
269+-- inspect $ 'copyStreamUtf8Lax `hasNoType` ''AT.FlattenState
270+-- inspect $ 'copyStreamUtf8Lax `hasNoType` ''D.ConcatMapUState
271+#endif
272+
273+o_1_space_copy_read :: BenchEnv -> [Benchmark]
274+o_1_space_copy_read env =
275+ [ bgroup "copy/read"
276+ [-- mkBench "rawToNull" env $ \inh _ ->
277+ -- copyStream inh (nullH env)
278+ -- , mkBench "rawToFile" env $ \inh outh ->
279+ -- copyStream inh outh
280+ -- This needs an ascii file, as decode just errors out.
281+ mkBench "SS.encodeLatin1' . SS.decodeLatin1" env $ \inh outh ->
282+ copyStreamLatin1' inh outh
283+ , mkBench "SS.encodeLatin1 . SS.decodeLatin1" env $ \inh outh ->
284+ copyStreamLatin1 inh outh
285+#ifdef DEVBUILD
286+ , mkBench "copyUtf8" env $ \inh outh ->
287+ _copyStreamUtf8' inh outh
288+#endif
289+ , mkBenchSmall "SS.encodeUtf8 . SS.decodeUtf8Lax" env $ \inh outh ->
290+ copyStreamUtf8 inh outh
291+ ]
292+ ]
293+
294+main :: IO ()
295+main = do
296+ (_, cfg, benches) <- parseCLIOpts defaultStreamSize
297+ env <- mkBenchEnv "Benchmark_FileSystem_Handle_InputFile"
298+ runMode (mode cfg) cfg benches (allBenchmarks env)
299+
300+ where
301+
302+ allBenchmarks env =
303+ [ bgroup (o_1_space_prefix moduleName) $ Prelude.concat $
304+ [ o_1_space_copy_chunked env
305+ , o_1_space_copy_read_group_ungroup env
306+ , o_1_space_copy_read env
307+ ]
308+ ]