on master help sign in
Add some word/unword, word counting benchmarks
661f842Harendra Kumar
.travis.yml+1−1
@@ -136,7 +136,7 @@ matrix:
136 #- env: BUILD=stack RESOLVER=nightly
136 #- env: BUILD=stack RESOLVER=nightly
137 # addons: {apt: {packages: [cabal-install-1.24], sources: [hvr-ghc]}}
137 # addons: {apt: {packages: [cabal-install-1.24], sources: [hvr-ghc]}}
138
138
139− - env: BUILD=stack RESOLVER=lts-13 GHCVER=8.6 STACK_BUILD_OPTIONS="--flag streamly:examples-sdl --flag streamly:benchmark --flag streamly:inspection"
139+ - env: BUILD=stack RESOLVER=lts-13 GHCVER=8.6 STACK_BUILD_OPTIONS="--flag streamly:examples-sdl --flag streamly:benchmark"
140 addons: {apt: {packages: [cabal-install-2.4,libsdl1.2-dev], sources: [hvr-ghc]}}
140 addons: {apt: {packages: [cabal-install-2.4,libsdl1.2-dev], sources: [hvr-ghc]}}
141
141
142 #- env: BUILD=stack RESOLVER=lts-12 GHCVER=8.4
142 #- env: BUILD=stack RESOLVER=lts-12 GHCVER=8.4
benchmark/FileIO.hs+12
@@ -79,6 +79,9 @@ main = do
79 , mkBench "linecount" href $ do
79 , mkBench "linecount" href $ do
80 Handles inh _ <- readIORef href
80 Handles inh _ <- readIORef href
81 BFA.countLines inh
81 BFA.countLines inh
82+ , mkBench "wordcount" href $ do
83+ Handles inh _ <- readIORef href
84+ BFA.countWords inh
82 , mkBench "sum" href $ do
85 , mkBench "sum" href $ do
83 Handles inh _ <- readIORef href
86 Handles inh _ <- readIORef href
84 BFA.sumBytes inh
87 BFA.sumBytes inh
@@ -99,6 +102,9 @@ main = do
99 , mkBench "linecountU" href $ do
102 , mkBench "linecountU" href $ do
100 Handles inh _ <- readIORef href
103 Handles inh _ <- readIORef href
101 BFS.countLinesU inh
104 BFS.countLinesU inh
105+ , mkBench "wordcount" href $ do
106+ Handles inh _ <- readIORef href
107+ BFS.countWords inh
102 , mkBench "sum" href $ do
108 , mkBench "sum" href $ do
103 Handles inh _ <- readIORef href
109 Handles inh _ <- readIORef href
104 BFS.sumBytes inh
110 BFS.sumBytes inh
@@ -155,6 +161,12 @@ main = do
155 , mkBench "words-unwords" href $ do
161 , mkBench "words-unwords" href $ do
156 Handles inh outh <- readIORef href
162 Handles inh outh <- readIORef href
157 BFS.wordsUnwordsCopy inh outh
163 BFS.wordsUnwordsCopy inh outh
164+ , mkBench "words-unwords-word8" href $ do
165+ Handles inh outh <- readIORef href
166+ BFS.wordsUnwordsCopyWord8 inh outh
167+ , mkBench "words-unwords-arrays" href $ do
168+ Handles inh outh <- readIORef href
169+ BFA.wordsUnwordsCopy inh outh
158 ]
170 ]
159
171
160 , bgroup "splitting"
172 , bgroup "splitting"
src/Streamly/Benchmark/FileIO/Array.hs+28
@@ -23,10 +23,12 @@ module Streamly.Benchmark.FileIO.Array
23 last
23 last
24 , countBytes
24 , countBytes
25 , countLines
25 , countLines
26+ , countWords
26 , sumBytes
27 , sumBytes
27 , cat
28 , cat
28 , copy
29 , copy
29 , linesUnlinesCopy
30 , linesUnlinesCopy
31+ , wordsUnwordsCopy
30 )
32 )
31where
33where
32
34
@@ -83,6 +85,17 @@ inspect $ hasNoTypeClasses 'countLines
83inspect $ 'countLines `hasNoType` ''Step
85inspect $ 'countLines `hasNoType` ''Step
84#endif
86#endif
85
87
88+-- XXX use a word splitting combinator instead of splitOn and test it.
89+-- | Count the number of lines in a file.
90+{-# INLINE countWords #-}
91+countWords :: Handle -> IO Int
92+countWords = S.length . A.splitOn 32 . FH.readArrays
93+
94+#ifdef INSPECTION
95+inspect $ hasNoTypeClasses 'countWords
96+inspect $ 'countWords `hasNoType` ''Step
97+#endif
98+
86-- | Sum the bytes in a file.
99-- | Sum the bytes in a file.
87{-# INLINE sumBytes #-}
100{-# INLINE sumBytes #-}
88sumBytes :: Handle -> IO Word8
101sumBytes :: Handle -> IO Word8
@@ -132,3 +145,18 @@ linesUnlinesCopy inh outh =
132inspect $ hasNoTypeClassesExcept 'linesUnlinesCopy [''Storable]
145inspect $ hasNoTypeClassesExcept 'linesUnlinesCopy [''Storable]
133-- inspect $ 'linesUnlinesCopy `hasNoType` ''Step
146-- inspect $ 'linesUnlinesCopy `hasNoType` ''Step
134#endif
147#endif
148+
149+-- | Words and unwords
150+{-# INLINE wordsUnwordsCopy #-}
151+wordsUnwordsCopy :: Handle -> Handle -> IO ()
152+wordsUnwordsCopy inh outh =
153+ S.runFold (FH.writeArraysInChunksOf (1024*1024) outh)
154+ $ S.intersperse (A.fromList [32])
155+ -- XXX use a word splitting combinator
156+ $ A.splitOn 32
157+ $ FH.readArraysOf (1024*1024) inh
158+
159+#ifdef INSPECTION
160+inspect $ hasNoTypeClassesExcept 'wordsUnwordsCopy [''Storable]
161+-- inspect $ 'wordsUnwordsCopy `hasNoType` ''Step
162+#endif
src/Streamly/Benchmark/FileIO/Stream.hs+72−23
@@ -25,11 +25,13 @@ module Streamly.Benchmark.FileIO.Stream
25 , countBytes
25 , countBytes
26 , countLines
26 , countLines
27 , countLinesU
27 , countLinesU
28+ , countWords
28 , sumBytes
29 , sumBytes
29 , cat
30 , cat
30 , catStreamWrite
31 , catStreamWrite
31 , copy
32 , copy
32 , linesUnlinesCopy
33 , linesUnlinesCopy
34+ , wordsUnwordsCopyWord8
33 , wordsUnwordsCopy
35 , wordsUnwordsCopy
34 , copyCodecChar8
36 , copyCodecChar8
35 , copyCodecUtf8
37 , copyCodecUtf8
@@ -105,6 +107,21 @@ inspect $ 'countLines `hasNoType` ''AT.FlattenState
105inspect $ 'countLines `hasNoType` ''D.ConcatMapUState
107inspect $ 'countLines `hasNoType` ''D.ConcatMapUState
106#endif
108#endif
107
109
110+-- | Count the number of words in a file.
111+{-# INLINE countWords #-}
112+countWords :: Handle -> IO Int
113+countWords =
114+ S.length
115+ . SS.foldWords FL.drain
116+ . SS.decodeChar8
117+ . FH.read
118+
119+#ifdef INSPECTION
120+inspect $ hasNoTypeClasses 'countWords
121+-- inspect $ 'countWords `hasNoType` ''Step
122+-- inspect $ 'countWords `hasNoType` ''D.ConcatMapUState
123+#endif
124+
108-- | Count the number of lines in a file.
125-- | Count the number of lines in a file.
109{-# INLINE countLinesU #-}
126{-# INLINE countLinesU #-}
110countLinesU :: Handle -> IO Int
127countLinesU :: Handle -> IO Int
@@ -253,14 +270,67 @@ inspect $ hasNoTypeClassesExcept 'linesUnlinesCopy [''Storable]
253-- inspect $ 'linesUnlinesCopy `hasNoType` ''D.ConcatMapUState
270-- inspect $ 'linesUnlinesCopy `hasNoType` ''D.ConcatMapUState
254#endif
271#endif
255
272
273+foreign import ccall unsafe "u_iswspace"
274+ iswspace :: Int -> Int
275+
276+-- Code copied from base/Data.Char to INLINE it
277+{-# INLINE isSpace #-}
278+isSpace :: Char -> Bool
279+-- isSpace includes non-breaking space
280+-- The magic 0x377 isn't really that magical. As of 2014, all the codepoints
281+-- at or below 0x377 have been assigned, so we shouldn't have to worry about
282+-- any new spaces appearing below there. It would probably be best to
283+-- use branchless ||, but currently the eqLit transformation will undo that,
284+-- so we'll do it like this until there's a way around that.
285+isSpace c
286+ | uc <= 0x377 = uc == 32 || uc - 0x9 <= 4 || uc == 0xa0
287+ | otherwise = iswspace (ord c) /= 0
288+ where
289+ uc = fromIntegral (ord c) :: Word
290+
291+{-# INLINE isSp #-}
292+isSp :: Word8 -> Bool
293+isSp = isSpace . chr . fromIntegral
294+
295+-- | Word, unwords and copy
296+{-# INLINE wordsUnwordsCopyWord8 #-}
297+wordsUnwordsCopyWord8 :: Handle -> Handle -> IO ()
298+wordsUnwordsCopyWord8 inh outh =
299+ S.runFold (FH.write outh)
300+ $ Internal.concatMapU Internal.fromList
301+ $ S.intersperse [32]
302+ $ S.wordsBy isSp FL.toList
303+ $ FH.read inh
304+
305+#ifdef INSPECTION
306+inspect $ hasNoTypeClasses 'wordsUnwordsCopyWord8
307+-- inspect $ 'wordsUnwordsCopyWord8 `hasNoType` ''Step
308+-- inspect $ 'wordsUnwordsCopyWord8 `hasNoType` ''D.ConcatMapUState
309+#endif
310+
256-- | Word, unwords and copy
311-- | Word, unwords and copy
257{-# INLINE wordsUnwordsCopy #-}
312{-# INLINE wordsUnwordsCopy #-}
258wordsUnwordsCopy :: Handle -> Handle -> IO ()
313wordsUnwordsCopy :: Handle -> Handle -> IO ()
259wordsUnwordsCopy inh outh =
314wordsUnwordsCopy inh outh =
260 S.runFold (FH.write outh)
315 S.runFold (FH.write outh)
261 $ SS.encodeChar8
316 $ SS.encodeChar8
317+ $ Internal.concatMapU Internal.fromList
318+ $ S.intersperse " "
319+ -- Array allocation is too expensive for such small strings. So just use
320+ -- lists instead.
321+ --
262− $ SS.unwords
322+ -- -- $ SS.unwords
263− $ SS.words
323+ -- -- $ SS.words
324+ --
325+ -- XXX This pipeline does not fuse with wordsBy but fuses with splitOn
326+ -- with -funfolding-use-threshold=300. With wordsBy it does not fuse
327+ -- even with high limits for inlining and spec-constr ghc options. With
328+ -- -funfolding-use-threshold=400 it performs pretty well and there
329+ -- is no evidence in the core that a join point involving Step
330+ -- constructors is not getting inlined. Not being able to fuse at all in
331+ -- this case could be an unknown issue, need more investigation.
332+ $ S.wordsBy isSpace FL.toList
333+ -- -- $ S.splitOn isSpace FL.toList
264 $ SS.decodeChar8
334 $ SS.decodeChar8
265 $ FH.read inh
335 $ FH.read inh
266
336
@@ -277,27 +347,6 @@ lf = fromIntegral (ord '\n')
277toarr :: String -> A.Array Word8
347toarr :: String -> A.Array Word8
278toarr = A.fromList . map (fromIntegral . ord)
348toarr = A.fromList . map (fromIntegral . ord)
279
349
280−foreign import ccall unsafe "u_iswspace"
281− iswspace :: Int -> Int
282−
283−-- Code copied from base/Data.Char to INLINE it
284−{-# INLINE isSpace #-}
285−isSpace :: Char -> Bool
286−-- isSpace includes non-breaking space
287−-- The magic 0x377 isn't really that magical. As of 2014, all the codepoints
288−-- at or below 0x377 have been assigned, so we shouldn't have to worry about
289−-- any new spaces appearing below there. It would probably be best to
290−-- use branchless ||, but currently the eqLit transformation will undo that,
291−-- so we'll do it like this until there's a way around that.
292−isSpace c
293− | uc <= 0x377 = uc == 32 || uc - 0x9 <= 4 || uc == 0xa0
294− | otherwise = iswspace (ord c) /= 0
295− where
296− uc = fromIntegral (ord c) :: Word
297−
298−isSp :: Word8 -> Bool
299−isSp = isSpace . chr . fromIntegral
300−
301-- | Split on line feed.
350-- | Split on line feed.
302{-# INLINE splitOn #-}
351{-# INLINE splitOn #-}
303splitOn :: Handle -> IO Int
352splitOn :: Handle -> IO Int
src/Streamly/Internal.hs+3−1
@@ -89,6 +89,7 @@ module Streamly.Internal
89
89
90 -- * Streamly.Unfold Experimental Exports
90 -- * Streamly.Unfold Experimental Exports
91 , Unfold (..)
91 , Unfold (..)
92+ , fromList
92
93
93 -- * Streamly.Memory.Array
94 -- * Streamly.Memory.Array
94 , readU
95 , readU
@@ -102,7 +103,8 @@ import Streamly.Streams.Combinators (inspectMode)
102import Streamly.Streams.Parallel (tapAsync, parallelFst, parallelMin)
103import Streamly.Streams.Parallel (tapAsync, parallelFst, parallelMin)
103import Streamly.Streams.Serial (wSerialFst, wSerialMin)
104import Streamly.Streams.Serial (wSerialFst, wSerialMin)
104import Streamly.Unfold.Types (Unfold(..))
105import Streamly.Unfold.Types (Unfold(..))
106+import Streamly.Unfold (fromList)
105
107
106import Streamly.Fold.Internal
108import Streamly.Fold.Internal
107import Streamly.Fold.Types
109import Streamly.Fold.Types
108−import Streamly.Prelude.Internal
110+import Streamly.Prelude.Internal hiding (fromList)
streamly.cabal+2
@@ -598,6 +598,8 @@ benchmark array
598benchmark fileio
598benchmark fileio
599 import: bench-options
599 import: bench-options
600 type: exitcode-stdio-1.0
600 type: exitcode-stdio-1.0
601+ -- A value of 400 works better for some benchmarks, however, it takes
602+ -- extraordinary amount of time to compile with that.
601 ghc-options: -funfolding-use-threshold=150
603 ghc-options: -funfolding-use-threshold=150
602 hs-source-dirs: benchmark
604 hs-source-dirs: benchmark
603 main-is: FileIO.hs
605 main-is: FileIO.hs