├── .github └── workflows │ └── build.yml ├── .packcheck.ignore ├── Benchmarks ├── ByteString.hs ├── ByteStringLazy.hs ├── Conduit.hs ├── DList.hs ├── Drinkery.hs ├── List.hs ├── ListT.hs ├── ListTransformer.hs ├── LogicT.hs ├── Machines.hs ├── Pipes.hs ├── Sequence.hs ├── SimpleConduit.hs ├── Streaming.hs ├── Streamly.hs ├── StreamlyArray.hs ├── StreamlyPure.hs ├── Text.hs ├── Vector.hs ├── VectorCommon.hs ├── VectorStorable.hs ├── VectorStreams.hs └── VectorUnboxed.hs ├── Changelog.md ├── LICENSE ├── README.md ├── appveyor.yml ├── bench-runner ├── Main.hs ├── bench-runner.cabal └── cabal.project.user ├── cabal.project.Werror ├── cabal.project.user ├── default.nix ├── docs └── benchmarking-notes.md ├── lib └── Benchmarks │ ├── BenchTH.hs │ ├── BenchmarkTH.hs │ ├── Common.hs │ └── DefaultMain.hs ├── stack.yaml └── streaming-benchmarks.cabal /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # packcheck-0.5.1 2 | # You can use any of the options supported by packcheck as environment 3 | # variables here. See https://github.com/composewell/packcheck for all 4 | # options and their explanation. 5 | 6 | name: packcheck 7 | 8 | #----------------------------------------------------------------------------- 9 | # Events on which the build should be triggered 10 | #----------------------------------------------------------------------------- 11 | 12 | on: 13 | push: 14 | branches: 15 | - master 16 | pull_request: 17 | 18 | 19 | #----------------------------------------------------------------------------- 20 | # Build matrix 21 | #----------------------------------------------------------------------------- 22 | 23 | jobs: 24 | build: 25 | name: >- 26 | ${{ matrix.name }} 27 | ${{ matrix.command }} 28 | ${{ matrix.runner }} 29 | ${{ matrix.ghc_version }} 30 | env: 31 | # ------------------------------------------------------------------------ 32 | # Common options 33 | # ------------------------------------------------------------------------ 34 | # GHC_OPTIONS: "-Werror" 35 | CABAL_REINIT_CONFIG: y 36 | LC_ALL: C.UTF-8 37 | 38 | # ------------------------------------------------------------------------ 39 | # What to build 40 | # ------------------------------------------------------------------------ 41 | DISABLE_TEST: "y" 42 | # DISABLE_BENCH: "y" 43 | # DISABLE_DOCS: "y" 44 | # DISABLE_SDIST_BUILD: "y" 45 | # DISABLE_SDIST_GIT_CHECK: "y" 46 | # DISABLE_DIST_CHECKS: "y" 47 | 48 | # ------------------------------------------------------------------------ 49 | # stack options 50 | # ------------------------------------------------------------------------ 51 | # Note requiring a specific version of stack using STACKVER may fail due to 52 | # github API limit while checking and upgrading/downgrading to the specific 53 | # version. 54 | #STACKVER: "1.6.5" 55 | #STACK_UPGRADE: "y" 56 | #RESOLVER: "lts-12" 57 | 58 | # ------------------------------------------------------------------------ 59 | # cabal options 60 | # ------------------------------------------------------------------------ 61 | CABAL_PROJECT: ${{ matrix.cabal_project }} 62 | CABAL_CHECK_RELAX: y 63 | CABAL_HACKAGE_MIRROR: "hackage.haskell.org:http://hackage.fpcomplete.com" 64 | 65 | # ------------------------------------------------------------------------ 66 | # Where to find the required tools 67 | # ------------------------------------------------------------------------ 68 | PATH: /opt/ghc/bin:/sbin:/usr/sbin:/bin:/usr/bin 69 | #TOOLS_DIR: /opt 70 | 71 | # ------------------------------------------------------------------------ 72 | # Location of packcheck.sh (the shell script invoked to perform CI tests ). 73 | # ------------------------------------------------------------------------ 74 | # You can either commit the packcheck.sh script at this path in your repo or 75 | # you can use it by specifying the PACKCHECK_REPO_URL option below in which 76 | # case it will be automatically copied from the packcheck repo to this path 77 | # during CI tests. In any case it is finally invoked from this path. 78 | PACKCHECK: "./packcheck.sh" 79 | # If you have not committed packcheck.sh in your repo at PACKCHECK 80 | # then it is automatically pulled from this URL. 81 | PACKCHECK_GITHUB_URL: "https://raw.githubusercontent.com/composewell/packcheck" 82 | PACKCHECK_GITHUB_COMMIT: "79fb4437009a7ebdada33d0493c27ee30160ec3f" 83 | 84 | # ------------------------------------------------------------------------ 85 | # Final build variables 86 | # ------------------------------------------------------------------------ 87 | PACKCHECK_COMMAND: ${{ matrix.command }} ${{ matrix.pack_options }} 88 | 89 | runs-on: ${{ matrix.runner }} 90 | strategy: 91 | fail-fast: false 92 | matrix: 93 | include: 94 | - name: ghc-8.10.4-sdist 95 | ghc_version: 8.10.4 96 | runner: ubuntu-latest 97 | command: cabal 98 | cabal_project: cabal.project.user 99 | - name: ghc-8.10.4-werror 100 | ghc_version: 8.10.4 101 | runner: ubuntu-latest 102 | command: cabal 103 | cabal_project: cabal.project.Werror 104 | #- name: ghc-8.10.4-macos-stack 105 | # ghc_version: 8.10.4 106 | # runner: macos-latest 107 | # command: stack 108 | # resolver: lts-18.0 109 | # #stack_yaml: stack.yaml 110 | # sdist_options: "--ignore-check" 111 | - name: ghc-8.8.4 112 | ghc_version: 8.8.4 113 | runner: ubuntu-latest 114 | command: cabal 115 | cabal_project: cabal.project.user 116 | steps: 117 | - uses: actions/checkout@v2 118 | 119 | - uses: haskell/actions/setup@v1 120 | if: ${{ matrix.name != 'hlint' }} 121 | with: 122 | ghc-version: ${{ matrix.ghc_version }} 123 | # We need not put this in the matrix. This will be fixed in most cases. 124 | cabal-version: 3.2 125 | 126 | - uses: actions/cache@v2 127 | name: Cache common directories 128 | with: 129 | path: | 130 | ~/.cabal 131 | ~/.ghc 132 | ~/.local 133 | ~/.stack 134 | key: ${{ matrix.ghc_version }}-${{ matrix.runner }} 135 | 136 | - name: Run installer 137 | if: ${{ matrix.installer != '' }} 138 | run: ${{ matrix.installer }} 139 | 140 | - name: Setup stack 141 | if: ${{ matrix.command == 'stack' }} 142 | run: | 143 | # required for packcheck 144 | sudo apt-get install -y curl 145 | # required for outbound https for stack and for stack setup 146 | sudo apt-get install -y netbase xz-utils make 147 | # If a custom stack-yaml is specified, replace the default with that 148 | if test -e "$STACK_YAML"; then rm -f stack.yaml && ln -sv $STACK_YAML stack.yaml; else true; fi 149 | unset STACK_YAML 150 | - name: Download packcheck 151 | run: | 152 | # Get packcheck if needed 153 | CURL=$(which curl) 154 | PACKCHECK_URL=${PACKCHECK_GITHUB_URL}/${PACKCHECK_GITHUB_COMMIT}/packcheck.sh 155 | if test ! -e "$PACKCHECK"; then $CURL -sL -o "$PACKCHECK" $PACKCHECK_URL; fi; 156 | chmod +x $PACKCHECK 157 | - name: Run packcheck 158 | run: | 159 | bash -c "$PACKCHECK $PACKCHECK_COMMAND" 160 | -------------------------------------------------------------------------------- /.packcheck.ignore: -------------------------------------------------------------------------------- 1 | .github/workflows/build.yml 2 | .packcheck.ignore 3 | Benchmarks/ListT.hs 4 | Benchmarks/ListTransformer.hs 5 | Benchmarks/LogicT.hs 6 | Benchmarks/SimpleConduit.hs 7 | appveyor.yml 8 | cabal.project.user 9 | cabal.project.Werror 10 | default.nix 11 | stack.yaml 12 | -------------------------------------------------------------------------------- /Benchmarks/ByteString.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : Benchmarks.ByteString 3 | -- Copyright : (c) 2018 Harendra Kumar 4 | -- 5 | -- License : MIT 6 | -- Maintainer : harendra.kumar@gmail.com 7 | 8 | {-# LANGUAGE ScopedTypeVariables #-} 9 | {-# LANGUAGE TemplateHaskell #-} 10 | 11 | module Benchmarks.ByteString where 12 | 13 | import Benchmarks.DefaultMain (defaultMain) 14 | -- import Benchmarks.Common (value, maxValue, appendValue) 15 | import Prelude (Int, (+), ($), (.), even, (>), (<=), subtract, undefined, 16 | maxBound, Maybe(..)) 17 | import qualified Prelude as P 18 | import Data.Word (Word8) 19 | 20 | import qualified Data.ByteString as S 21 | 22 | nElements, nAppends :: Int 23 | nElements = 1000000 24 | nAppends = 10000 25 | 26 | minElem, maxElem :: Word8 27 | minElem = 1 28 | maxElem = maxBound 29 | 30 | ------------------------------------------------------------------------------- 31 | -- Stream generation and elimination 32 | ------------------------------------------------------------------------------- 33 | 34 | type Element = Word8 35 | type Stream a = S.ByteString 36 | 37 | {-# INLINE sourceN #-} 38 | sourceN :: Int -> Int -> Stream Element 39 | sourceN count begin = S.unfoldr step begin 40 | where 41 | step i = 42 | if i > begin + count 43 | then Nothing 44 | else (Just (P.fromIntegral i, i + 1)) 45 | 46 | {-# INLINE source #-} 47 | source :: Int -> Stream Element 48 | source = sourceN nElements 49 | 50 | ------------------------------------------------------------------------------- 51 | -- Append 52 | ------------------------------------------------------------------------------- 53 | 54 | {-# INLINE appendSourceR #-} 55 | appendSourceR :: Int -> () 56 | appendSourceR n = 57 | toNull $ P.foldr (S.append) S.empty (P.map (S.singleton . P.fromIntegral) [n..n+nAppends]) 58 | 59 | {-# INLINE appendSourceL #-} 60 | appendSourceL :: Int -> () 61 | appendSourceL n = 62 | toNull $ P.foldl (S.append) S.empty (P.map (S.singleton . P.fromIntegral) [n..n+nAppends]) 63 | 64 | ------------------------------------------------------------------------------- 65 | -- Elimination 66 | ------------------------------------------------------------------------------- 67 | 68 | -- Using NFData for evaluation may be fraught with problems because of a 69 | -- non-optimal implementation of NFData instance. So we just evaluate each 70 | -- element of the stream using a fold. 71 | {-# INLINE eval #-} 72 | eval :: Stream a -> () 73 | eval = S.foldr P.seq () 74 | 75 | -- eval foldable 76 | {-# INLINE evalF #-} 77 | evalF :: P.Foldable t => t a -> () 78 | evalF = P.foldr P.seq () 79 | 80 | {-# INLINE toNull #-} 81 | toNull :: Stream Element -> () 82 | toNull = eval 83 | 84 | {-# INLINE toList #-} 85 | toList :: Stream Element -> () 86 | toList = evalF . S.unpack 87 | 88 | {-# INLINE foldl #-} 89 | foldl :: Stream Element -> Element 90 | foldl = S.foldl' (+) 0 91 | 92 | {-# INLINE last #-} 93 | last :: Stream Element -> Element 94 | last = S.last 95 | 96 | ------------------------------------------------------------------------------- 97 | -- Transformation 98 | ------------------------------------------------------------------------------- 99 | 100 | {-# INLINE transform #-} 101 | transform :: Stream a -> () 102 | transform = eval 103 | 104 | {-# INLINE composeN #-} 105 | composeN :: Int 106 | -> (Stream Element -> Stream Element) 107 | -> Stream Element 108 | -> () 109 | composeN n f = 110 | case n of 111 | 1 -> transform . f 112 | 2 -> transform . f . f 113 | 3 -> transform . f . f . f 114 | 4 -> transform . f . f . f . f 115 | _ -> undefined 116 | 117 | {-# INLINE scan #-} 118 | {-# INLINE map #-} 119 | {-# INLINE mapM #-} 120 | {-# INLINE filterEven #-} 121 | {-# INLINE filterAllOut #-} 122 | {-# INLINE filterAllIn #-} 123 | {-# INLINE takeOne #-} 124 | {-# INLINE takeAll #-} 125 | {-# INLINE takeWhileTrue #-} 126 | {-# INLINE dropOne #-} 127 | {-# INLINE dropAll #-} 128 | {-# INLINE dropWhileTrue #-} 129 | {-# INLINE dropWhileFalse #-} 130 | scan, map, mapM, 131 | filterEven, filterAllOut, filterAllIn, 132 | takeOne, takeAll, takeWhileTrue, 133 | dropOne, dropAll, dropWhileTrue, dropWhileFalse 134 | :: Int -> Stream Int -> () 135 | 136 | -- XXX there is no scanl' 137 | scan n = composeN n $ S.scanl (+) 0 138 | map n = composeN n $ S.map (+1) 139 | mapM = map 140 | filterEven n = composeN n $ S.filter even 141 | filterAllOut n = composeN n $ S.filter (> maxElem) 142 | filterAllIn n = composeN n $ S.filter (<= maxElem) 143 | takeOne n = composeN n $ S.take 1 144 | takeAll n = composeN n $ S.take nElements 145 | takeWhileTrue n = composeN n $ S.takeWhile (<= maxElem) 146 | dropOne n = composeN n $ S.drop 1 147 | dropAll n = composeN n $ S.drop nElements 148 | dropWhileFalse n = composeN n $ S.dropWhile (> maxElem) 149 | dropWhileTrue n = composeN n $ S.dropWhile (<= maxElem) 150 | 151 | ------------------------------------------------------------------------------- 152 | -- Iteration 153 | ------------------------------------------------------------------------------- 154 | 155 | iterStreamLen, maxIters :: Int 156 | iterStreamLen = 10 157 | maxIters = 100000 158 | 159 | {-# INLINE iterateSource #-} 160 | iterateSource :: (Stream Element -> Stream Element) 161 | -> Int 162 | -> Int 163 | -> Stream Element 164 | iterateSource g i n = f i (sourceN iterStreamLen n) 165 | where 166 | f (0 :: Int) m = g m 167 | f x m = g (f (x P.- 1) m) 168 | 169 | {-# INLINE iterateScan #-} 170 | {-# INLINE iterateFilterEven #-} 171 | {-# INLINE iterateTakeAll #-} 172 | {-# INLINE iterateDropOne #-} 173 | {-# INLINE iterateDropWhileFalse #-} 174 | {-# INLINE iterateDropWhileTrue #-} 175 | iterateScan, iterateFilterEven, iterateTakeAll, iterateDropOne, 176 | iterateDropWhileFalse, iterateDropWhileTrue :: Int -> Stream Element 177 | 178 | -- XXX using scanl instead of scanl' 179 | -- Scan increases the size of the stream by 1, drop 1 to not blow up the size 180 | -- due to many iterations. 181 | iterateScan n = iterateSource (S.drop 1 . S.scanl (+) 0) maxIters n 182 | iterateFilterEven n = iterateSource (S.filter even) maxIters n 183 | iterateTakeAll n = iterateSource (S.take nElements) maxIters n 184 | iterateDropOne n = iterateSource (S.drop 1) maxIters n 185 | iterateDropWhileFalse n = iterateSource (S.dropWhile (> maxElem)) maxIters n 186 | iterateDropWhileTrue n = iterateSource (S.dropWhile (<= maxElem)) maxIters n 187 | 188 | ------------------------------------------------------------------------------- 189 | -- Mixed Composition 190 | ------------------------------------------------------------------------------- 191 | 192 | {-# INLINE scanMap #-} 193 | {-# INLINE dropMap #-} 194 | {-# INLINE dropScan #-} 195 | {-# INLINE takeDrop #-} 196 | {-# INLINE takeScan #-} 197 | {-# INLINE takeMap #-} 198 | {-# INLINE filterDrop #-} 199 | {-# INLINE filterTake #-} 200 | {-# INLINE filterScan #-} 201 | {-# INLINE filterMap #-} 202 | scanMap, dropMap, dropScan, takeDrop, takeScan, takeMap, filterDrop, 203 | filterTake, filterScan, filterMap 204 | :: Int -> Stream Element -> () 205 | 206 | -- XXX using scanl instead of scanl' 207 | scanMap n = composeN n $ S.map (subtract 1) . S.scanl (+) 0 208 | dropMap n = composeN n $ S.map (subtract 1) . S.drop 1 209 | dropScan n = composeN n $ S.scanl (+) 0 . S.drop 1 210 | takeDrop n = composeN n $ S.drop 1 . S.take nElements 211 | takeScan n = composeN n $ S.scanl (+) 0 . S.take nElements 212 | takeMap n = composeN n $ S.map (subtract 1) . S.take nElements 213 | filterDrop n = composeN n $ S.drop 1 . S.filter (<= maxElem) 214 | filterTake n = composeN n $ S.take nElements . S.filter (<= maxElem) 215 | filterScan n = composeN n $ S.scanl (+) 0 . S.filter (<= maxElem) 216 | filterMap n = composeN n $ S.map (subtract 1) . S.filter (<= maxElem) 217 | 218 | ------------------------------------------------------------------------------- 219 | -- Zipping and concat 220 | ------------------------------------------------------------------------------- 221 | 222 | {-# INLINE zip #-} 223 | zip :: Stream Element -> () 224 | zip src = P.foldr (\(x,y) xs -> P.seq x (P.seq y xs)) () 225 | $ S.zipWith (,) src src 226 | 227 | {-# INLINE concatMap #-} 228 | concatMap :: Stream Element -> () 229 | concatMap src = transform $ (S.concatMap (S.replicate 3) src) 230 | 231 | main :: P.IO () 232 | main = $(defaultMain "ByteString") 233 | -------------------------------------------------------------------------------- /Benchmarks/ByteStringLazy.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : Benchmarks.ByteStringLazy 3 | -- Copyright : (c) 2019 Harendra Kumar 4 | -- 5 | -- License : MIT 6 | -- Maintainer : harendra.kumar@gmail.com 7 | 8 | {-# LANGUAGE ScopedTypeVariables #-} 9 | {-# LANGUAGE TemplateHaskell #-} 10 | 11 | module Benchmarks.ByteStringLazy where 12 | 13 | import Benchmarks.DefaultMain (defaultMain) 14 | -- import Benchmarks.Common (value, maxValue, appendValue) 15 | import Prelude (Int, (+), ($), (.), even, (>), (<=), subtract, undefined, 16 | maxBound, Maybe(..)) 17 | import qualified Prelude as P 18 | import Data.Word (Word8) 19 | import Data.Int (Int64) -- for lazy bytestring 20 | 21 | import qualified Data.ByteString.Lazy as S 22 | 23 | nAppends :: Int 24 | nElements :: Int64 25 | nElements = 1000000 26 | nAppends = 10000 27 | 28 | minElem, maxElem :: Word8 29 | minElem = 1 30 | maxElem = maxBound 31 | 32 | ------------------------------------------------------------------------------- 33 | -- Stream generation and elimination 34 | ------------------------------------------------------------------------------- 35 | 36 | type Element = Word8 37 | type Stream a = S.ByteString 38 | 39 | {-# INLINE sourceN #-} 40 | sourceN :: Int -> Int -> Stream Element 41 | sourceN count begin = S.unfoldr step begin 42 | where 43 | step i = 44 | if i > begin + count 45 | then Nothing 46 | else (Just (P.fromIntegral i, i + 1)) 47 | 48 | {-# INLINE source #-} 49 | source :: Int -> Stream Element 50 | source = sourceN (P.fromIntegral nElements) 51 | 52 | ------------------------------------------------------------------------------- 53 | -- Append 54 | ------------------------------------------------------------------------------- 55 | 56 | {-# INLINE appendSourceR #-} 57 | appendSourceR :: Int -> () 58 | appendSourceR n = 59 | toNull $ P.foldr (S.append) S.empty (P.map (S.singleton . P.fromIntegral) [n..n+nAppends]) 60 | 61 | {-# INLINE appendSourceL #-} 62 | appendSourceL :: Int -> () 63 | appendSourceL n = 64 | toNull $ P.foldl (S.append) S.empty (P.map (S.singleton . P.fromIntegral) [n..n+nAppends]) 65 | 66 | ------------------------------------------------------------------------------- 67 | -- Elimination 68 | ------------------------------------------------------------------------------- 69 | 70 | -- Using NFData for evaluation may be fraught with problems because of a 71 | -- non-optimal implementation of NFData instance. So we just evaluate each 72 | -- element of the stream using a fold. 73 | {-# INLINE eval #-} 74 | eval :: Stream a -> () 75 | eval = S.foldr P.seq () 76 | 77 | -- eval foldable 78 | {-# INLINE evalF #-} 79 | evalF :: P.Foldable t => t a -> () 80 | evalF = P.foldr P.seq () 81 | 82 | {-# INLINE toNull #-} 83 | toNull :: Stream Element -> () 84 | toNull = eval 85 | 86 | {-# INLINE toList #-} 87 | toList :: Stream Element -> () 88 | toList = evalF . S.unpack 89 | 90 | {-# INLINE foldl #-} 91 | foldl :: Stream Element -> Element 92 | foldl = S.foldl' (+) 0 93 | 94 | {-# INLINE last #-} 95 | last :: Stream Element -> Element 96 | last = S.last 97 | 98 | ------------------------------------------------------------------------------- 99 | -- Transformation 100 | ------------------------------------------------------------------------------- 101 | 102 | {-# INLINE transform #-} 103 | transform :: Stream a -> () 104 | transform = eval 105 | 106 | {-# INLINE composeN #-} 107 | composeN :: Int 108 | -> (Stream Element -> Stream Element) 109 | -> Stream Element 110 | -> () 111 | composeN n f = 112 | case n of 113 | 1 -> transform . f 114 | 2 -> transform . f . f 115 | 3 -> transform . f . f . f 116 | 4 -> transform . f . f . f . f 117 | _ -> undefined 118 | 119 | {-# INLINE map #-} 120 | {-# INLINE mapM #-} 121 | {-# INLINE filterEven #-} 122 | {-# INLINE filterAllOut #-} 123 | {-# INLINE filterAllIn #-} 124 | {-# INLINE takeOne #-} 125 | {-# INLINE takeAll #-} 126 | {-# INLINE takeWhileTrue #-} 127 | {-# INLINE dropOne #-} 128 | {-# INLINE dropAll #-} 129 | {-# INLINE dropWhileTrue #-} 130 | {-# INLINE dropWhileFalse #-} 131 | map, mapM, 132 | filterEven, filterAllOut, filterAllIn, 133 | takeOne, takeAll, takeWhileTrue, 134 | dropOne, dropAll, dropWhileTrue, dropWhileFalse 135 | :: Int -> Stream Int -> () 136 | 137 | -- XXX there is no scanl' 138 | -- XXX All scan ops hang for lazy bytestring, disabled for now 139 | {- 140 | {-# INLINE scan #-} 141 | scan :: Int -> Stream Int -> () 142 | scan n = composeN n $ S.scanl (+) 0 143 | -} 144 | map n = composeN n $ S.map (+1) 145 | mapM = map 146 | filterEven n = composeN n $ S.filter even 147 | filterAllOut n = composeN n $ S.filter (> maxElem) 148 | filterAllIn n = composeN n $ S.filter (<= maxElem) 149 | takeOne n = composeN n $ S.take 1 150 | takeAll n = composeN n $ S.take nElements 151 | takeWhileTrue n = composeN n $ S.takeWhile (<= maxElem) 152 | dropOne n = composeN n $ S.drop 1 153 | dropAll n = composeN n $ S.drop nElements 154 | dropWhileFalse n = composeN n $ S.dropWhile (> maxElem) 155 | dropWhileTrue n = composeN n $ S.dropWhile (<= maxElem) 156 | 157 | ------------------------------------------------------------------------------- 158 | -- Iteration 159 | ------------------------------------------------------------------------------- 160 | 161 | iterStreamLen, maxIters :: Int 162 | iterStreamLen = 10 163 | maxIters = 100000 164 | 165 | {-# INLINE iterateSource #-} 166 | iterateSource :: (Stream Element -> Stream Element) 167 | -> Int 168 | -> Int 169 | -> Stream Element 170 | iterateSource g i n = f i (sourceN iterStreamLen n) 171 | where 172 | f (0 :: Int) m = g m 173 | f x m = g (f (x P.- 1) m) 174 | 175 | {-# INLINE iterateFilterEven #-} 176 | {-# INLINE iterateTakeAll #-} 177 | {-# INLINE iterateDropOne #-} 178 | {-# INLINE iterateDropWhileFalse #-} 179 | {-# INLINE iterateDropWhileTrue #-} 180 | iterateFilterEven, iterateTakeAll, iterateDropOne, 181 | iterateDropWhileFalse, iterateDropWhileTrue :: Int -> Stream Element 182 | 183 | -- this is quadratic 184 | -- XXX using scanl instead of scanl' 185 | -- XXX All scan ops hang for lazy bytestring, disabled for now 186 | {- 187 | -- Scan increases the size of the stream by 1, drop 1 to not blow up the size 188 | -- due to many iterations. 189 | {-# INLINE iterateScan #-} 190 | iterateScan :: Int -> Stream Element 191 | iterateScan n = iterateSource (error "hangs") maxIters n 192 | -} 193 | iterateFilterEven n = iterateSource (S.filter even) maxIters n 194 | iterateTakeAll n = iterateSource (S.take nElements) maxIters n 195 | iterateDropOne n = iterateSource (S.drop 1) maxIters n 196 | iterateDropWhileFalse n = iterateSource (S.dropWhile (> maxElem)) maxIters n 197 | iterateDropWhileTrue n = iterateSource (S.dropWhile (<= maxElem)) maxIters n 198 | 199 | ------------------------------------------------------------------------------- 200 | -- Mixed Composition 201 | ------------------------------------------------------------------------------- 202 | 203 | {-# INLINE dropMap #-} 204 | {-# INLINE takeDrop #-} 205 | {-# INLINE takeMap #-} 206 | {-# INLINE filterDrop #-} 207 | {-# INLINE filterTake #-} 208 | {-# INLINE filterMap #-} 209 | dropMap, takeDrop, takeMap, filterDrop, 210 | filterTake, filterMap 211 | :: Int -> Stream Element -> () 212 | 213 | -- XXX using scanl instead of scanl' 214 | -- XXX All scan ops hang for lazy bytestring, disabled for now 215 | -- {-# INLINE scanMap #-} 216 | -- scanMap n = composeN n $ id 217 | dropMap n = composeN n $ S.map (subtract 1) . S.drop 1 218 | -- {-# INLINE dropScan #-} 219 | -- dropScan n = composeN n $ id 220 | takeDrop n = composeN n $ S.drop 1 . S.take nElements 221 | -- {-# INLINE takeScan #-} 222 | -- takeScan n = composeN n $ id 223 | takeMap n = composeN n $ S.map (subtract 1) . S.take nElements 224 | filterDrop n = composeN n $ S.drop 1 . S.filter (<= maxElem) 225 | filterTake n = composeN n $ S.take nElements . S.filter (<= maxElem) 226 | -- {-# INLINE filterScan #-} 227 | -- filterScan n = composeN n $ id 228 | filterMap n = composeN n $ S.map (subtract 1) . S.filter (<= maxElem) 229 | 230 | ------------------------------------------------------------------------------- 231 | -- Zipping and concat 232 | ------------------------------------------------------------------------------- 233 | 234 | {-# INLINE zip #-} 235 | zip :: Stream Element -> () 236 | zip src = P.foldr (\(x,y) xs -> P.seq x (P.seq y xs)) () 237 | $ S.zipWith (,) src src 238 | 239 | {-# INLINE concatMap #-} 240 | concatMap :: Stream Element -> () 241 | concatMap src = transform $ (S.concatMap (S.replicate 3) src) 242 | 243 | main :: P.IO () 244 | main = $(defaultMain "ByteStringLazy") 245 | -------------------------------------------------------------------------------- /Benchmarks/Conduit.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : Benchmarks.Conduit 3 | -- Copyright : (c) 2018 Harendra Kumar 4 | -- 5 | -- License : MIT 6 | -- Maintainer : harendra.kumar@gmail.com 7 | 8 | {-# LANGUAGE TemplateHaskell #-} 9 | 10 | module Benchmarks.Conduit where 11 | 12 | import Benchmarks.DefaultMain (defaultMain) 13 | import Benchmarks.Common (value, maxValue, appendValue) 14 | import Prelude 15 | (Monad, Int, (+), ($), return, even, (>), (<=), 16 | subtract, undefined, replicate, (<$>), (<*>), Maybe(..), foldMap, (.), 17 | maxBound) 18 | import qualified Prelude as P 19 | import Data.Semigroup ((<>)) 20 | 21 | import qualified Data.Conduit as S 22 | import qualified Data.Conduit.Combinators as S 23 | import qualified Data.Conduit.List as SL 24 | -- import Data.Conduit.List (sourceList) 25 | 26 | ------------------------------------------------------------------------------- 27 | -- Stream generation and elimination 28 | ------------------------------------------------------------------------------- 29 | 30 | type Source m i a = S.ConduitT i a m () 31 | type Sink m a r = S.ConduitT a S.Void m r 32 | type Pipe m a b = S.ConduitT a b m () 33 | 34 | {-# INLINE source #-} 35 | source :: Monad m => Int -> Source m () Int 36 | -- source n = sourceList [n..n+value] 37 | source n = SL.unfoldM step n 38 | where 39 | step cnt = 40 | if cnt > n + value 41 | then return Nothing 42 | else return (Just (cnt, cnt + 1)) 43 | 44 | ------------------------------------------------------------------------------- 45 | -- Append 46 | ------------------------------------------------------------------------------- 47 | 48 | {-# INLINE appendSourceR #-} 49 | appendSourceR :: Int -> P.IO () 50 | appendSourceR n = 51 | toNull $ foldMap (S.yieldM . return) [n..n+appendValue] 52 | 53 | {-# INLINE appendSourceL #-} 54 | appendSourceL :: Int -> P.IO () 55 | appendSourceL n = 56 | toNull $ P.foldl (<>) P.mempty (P.map (S.yieldM . return) [n..n+appendValue]) 57 | 58 | ------------------------------------------------------------------------------- 59 | -- Elimination 60 | ------------------------------------------------------------------------------- 61 | 62 | -- eval :: Monad m => Sink m a () 63 | -- eval = S.foldl (\_ x -> P.seq x ()) () 64 | 65 | {-# INLINE runStream #-} 66 | -- runStream :: Monad m => Sink m Int () -> Source m () Int -> m () 67 | -- runStream t src = S.runConduit $ src S..| t S..| S.foldl (\_ x -> P.seq x ()) () 68 | runStream :: Monad m => Sink m Int a -> Source m () Int -> m a 69 | runStream t src = S.runConduit $ src S..| t 70 | 71 | {-# INLINE toNull #-} 72 | toNull :: Monad m => Source m () Int -> m () 73 | toNull = runStream $ S.sinkNull 74 | -- toNull src = S.runConduit $ src S..| eval 75 | 76 | {-# INLINE toList #-} 77 | toList :: Monad m => Source m () Int -> m [Int] 78 | toList = runStream $ S.sinkList 79 | 80 | {-# INLINE foldl #-} 81 | foldl :: Monad m => Source m () Int -> m Int 82 | foldl = runStream $ S.foldl (+) 0 83 | 84 | {-# INLINE last #-} 85 | last :: Monad m => Source m () Int -> m (Maybe Int) 86 | last = runStream $ S.last 87 | 88 | ------------------------------------------------------------------------------- 89 | -- Transformation 90 | ------------------------------------------------------------------------------- 91 | 92 | {-# INLINE transform #-} 93 | transform :: Monad m => Pipe m Int Int -> Source m () Int -> m () 94 | -- mapM_ is much more costly compared to sinkNull 95 | -- transform t = runStream (t S..| S.mapM_ (\_ -> return ())) 96 | -- transform t s = runStream (t S..| eval) s P.>>= \x -> P.seq x (return ()) 97 | transform t = runStream (t S..| S.sinkNull) 98 | 99 | {-# INLINE composeN #-} 100 | composeN :: Monad m => Int -> Pipe m Int Int -> Source m () Int -> m () 101 | composeN n f = 102 | case n of 103 | 1 -> transform $ f 104 | 2 -> transform $ f S..| f 105 | 3 -> transform $ f S..| f S..| f 106 | 4 -> transform $ f S..| f S..| f S..| f 107 | _ -> undefined 108 | 109 | {-# INLINE scan #-} 110 | {-# INLINE map #-} 111 | {-# INLINE mapM #-} 112 | {-# INLINE filterEven #-} 113 | {-# INLINE filterAllOut #-} 114 | {-# INLINE filterAllIn #-} 115 | {-# INLINE takeOne #-} 116 | {-# INLINE takeAll #-} 117 | {-# INLINE takeWhileTrue #-} 118 | {-# INLINE dropOne #-} 119 | {-# INLINE dropAll #-} 120 | {-# INLINE dropWhileTrue #-} 121 | {-# INLINE dropWhileFalse #-} 122 | scan, map, mapM, 123 | filterEven, filterAllOut, filterAllIn, 124 | takeOne, takeAll, takeWhileTrue, 125 | dropOne, dropAll, dropWhileTrue, dropWhileFalse 126 | :: Monad m => Int -> Source m () Int -> m () 127 | 128 | scan n = composeN n $ S.scanl (+) 0 129 | map n = composeN n $ S.map (+1) 130 | mapM n = composeN n $ S.mapM return 131 | filterEven n = composeN n $ S.filter even 132 | filterAllOut n = composeN n $ S.filter (> maxValue) 133 | filterAllIn n = composeN n $ S.filter (<= maxValue) 134 | takeOne n = composeN n $ S.take 1 135 | takeAll n = composeN n $ S.take maxValue 136 | takeWhileTrue n = composeN n $ S.takeWhile (<= maxValue) 137 | dropOne n = composeN n $ (S.drop 1 P.>> S.awaitForever S.yield) 138 | dropAll n = composeN n $ (S.drop maxValue P.>> S.awaitForever S.yield) 139 | dropWhileFalse n = composeN n $ (S.dropWhile (> maxValue) P.>> 140 | S.awaitForever S.yield) 141 | dropWhileTrue n = composeN n $ (S.dropWhile (<= maxValue) P.>> 142 | S.awaitForever S.yield) 143 | 144 | ------------------------------------------------------------------------------- 145 | -- Mixed Composition 146 | ------------------------------------------------------------------------------- 147 | 148 | {-# INLINE scanMap #-} 149 | {-# INLINE dropMap #-} 150 | {-# INLINE dropScan #-} 151 | {-# INLINE takeDrop #-} 152 | {-# INLINE takeScan #-} 153 | {-# INLINE takeMap #-} 154 | {-# INLINE filterDrop #-} 155 | {-# INLINE filterTake #-} 156 | {-# INLINE filterScan #-} 157 | {-# INLINE filterMap #-} 158 | scanMap, dropMap, dropScan, takeDrop, takeScan, takeMap, filterDrop, 159 | filterTake, filterScan, filterMap 160 | :: Monad m => Int -> Source m () Int -> m () 161 | 162 | scanMap n = composeN n $ S.map (subtract 1) S..| S.scanl (+) 0 163 | dropMap n = composeN n $ S.map (subtract 1) S..| 164 | (S.drop 1 P.>> S.awaitForever S.yield) 165 | dropScan n = composeN n $ S.scanl (+) 0 S..| 166 | (S.drop 1 P.>> S.awaitForever S.yield) 167 | takeDrop n = composeN n $ (S.drop 1 P.>> S.awaitForever S.yield) S..| 168 | S.take maxValue 169 | takeScan n = composeN n $ S.scanl (+) 0 S..| S.take maxValue 170 | takeMap n = composeN n $ S.map (subtract 1) S..| S.take maxValue 171 | filterDrop n = composeN n $ (S.drop 1 P.>> S.awaitForever S.yield) S..| 172 | S.filter (<= maxValue) 173 | filterTake n = composeN n $ S.take maxValue S..| S.filter (<= maxValue) 174 | filterScan n = composeN n $ S.scanl (+) 0 S..| S.filter (<= maxBound) 175 | filterMap n = composeN n $ S.map (subtract 1) S..| S.filter (<= maxValue) 176 | 177 | ------------------------------------------------------------------------------- 178 | -- Zipping and concat 179 | ------------------------------------------------------------------------------- 180 | 181 | {-# INLINE zip #-} 182 | zip :: Monad m => Source m () Int -> m () 183 | 184 | zip src = S.runConduit $ 185 | ( S.getZipSource $ (,) 186 | <$> S.ZipSource src 187 | <*> S.ZipSource src) S..| S.sinkNull -- eval 188 | 189 | {-# INLINE concatMapFoldable #-} 190 | concatMapFoldable :: Monad m => Source m () Int -> m () 191 | concatMapFoldable = transform (S.map (replicate 3) S..| S.concat) 192 | 193 | main :: P.IO () 194 | main = $(defaultMain "Conduit") 195 | -------------------------------------------------------------------------------- /Benchmarks/DList.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : Benchmarks.DList 3 | -- Copyright : (c) 2018 Harendra Kumar 4 | -- 5 | -- License : MIT 6 | -- Maintainer : harendra.kumar@gmail.com 7 | 8 | {-# LANGUAGE ScopedTypeVariables #-} 9 | {-# LANGUAGE TemplateHaskell #-} 10 | 11 | module Benchmarks.DList where 12 | 13 | import Benchmarks.DefaultMain (defaultMain) 14 | import Benchmarks.Common (value, appendValue) 15 | import Prelude (Int, (+), ($), (.), (>), undefined, Maybe(..)) 16 | import qualified Prelude as P 17 | import qualified Data.Foldable as P 18 | 19 | import qualified Data.DList as S 20 | 21 | ------------------------------------------------------------------------------- 22 | -- Stream generation and elimination 23 | ------------------------------------------------------------------------------- 24 | 25 | type Stream = S.DList 26 | 27 | {-# INLINE source #-} 28 | source :: Int -> Stream Int 29 | -- source v = [v..v+value] 30 | 31 | source n = S.unfoldr step n 32 | where 33 | step cnt = 34 | if cnt > n + value 35 | then Nothing 36 | else (Just (cnt, cnt + 1)) 37 | 38 | {-# INLINE sourceN #-} 39 | sourceN :: Int -> Int -> Stream Int 40 | sourceN count begin = S.unfoldr step begin 41 | where 42 | step i = 43 | if i > begin + count 44 | then Nothing 45 | else (Just (i, i + 1)) 46 | 47 | ------------------------------------------------------------------------------- 48 | -- Append 49 | ------------------------------------------------------------------------------- 50 | 51 | {-# INLINE appendSourceR #-} 52 | appendSourceR :: Int -> () 53 | appendSourceR n = 54 | toNull $ P.foldr S.append S.empty (P.map S.singleton [n..n+appendValue]) 55 | 56 | {-# INLINE appendSourceL #-} 57 | appendSourceL :: Int -> () 58 | appendSourceL n = 59 | toNull $ P.foldl S.append S.empty (P.map S.singleton [n..n+appendValue]) 60 | 61 | ------------------------------------------------------------------------------- 62 | -- Elimination 63 | ------------------------------------------------------------------------------- 64 | 65 | -- Using NFData for evaluation may be fraught with problems because of a 66 | -- non-optimal implementation of NFData instance. So we just evaluate each 67 | -- element of the stream using a fold. 68 | {-# INLINE eval #-} 69 | eval :: Stream a -> () 70 | eval = S.foldr P.seq () 71 | 72 | -- eval foldable 73 | {-# INLINE evalF #-} 74 | evalF :: P.Foldable t => t a -> () 75 | evalF = P.foldr P.seq () 76 | 77 | {-# INLINE toNull #-} 78 | toNull :: Stream Int -> () 79 | toNull = eval 80 | 81 | {-# INLINE toList #-} 82 | toList :: Stream Int -> () 83 | toList = evalF . S.toList 84 | 85 | {-# INLINE foldl #-} 86 | foldl :: Stream Int -> Int 87 | foldl = P.foldl' (+) 0 88 | 89 | {-# INLINE last #-} 90 | last :: Stream Int -> Int 91 | last = undefined 92 | 93 | ------------------------------------------------------------------------------- 94 | -- Transformation 95 | ------------------------------------------------------------------------------- 96 | 97 | {-# INLINE transform #-} 98 | transform :: Stream a -> () 99 | transform = eval 100 | 101 | {-# INLINE composeN #-} 102 | composeN :: Int -> (Stream Int -> Stream Int) -> Stream Int -> () 103 | composeN n f = 104 | case n of 105 | 1 -> transform . f 106 | 2 -> transform . f . f 107 | 3 -> transform . f . f . f 108 | 4 -> transform . f . f . f . f 109 | _ -> undefined 110 | 111 | {-# INLINE scan #-} 112 | {-# INLINE map #-} 113 | {-# INLINE mapM #-} 114 | {-# INLINE filterEven #-} 115 | {-# INLINE filterAllOut #-} 116 | {-# INLINE filterAllIn #-} 117 | {-# INLINE takeOne #-} 118 | {-# INLINE takeAll #-} 119 | {-# INLINE takeWhileTrue #-} 120 | {-# INLINE dropOne #-} 121 | {-# INLINE dropAll #-} 122 | {-# INLINE dropWhileTrue #-} 123 | {-# INLINE dropWhileFalse #-} 124 | scan, map, mapM, 125 | filterEven, filterAllOut, filterAllIn, 126 | takeOne, takeAll, takeWhileTrue, 127 | dropOne, dropAll, dropWhileTrue, dropWhileFalse 128 | :: Int -> Stream Int -> () 129 | 130 | scan = undefined 131 | map n = composeN n $ S.map (+1) 132 | mapM = map 133 | filterEven = undefined 134 | filterAllOut = undefined 135 | filterAllIn = undefined 136 | takeOne = undefined 137 | takeAll = undefined 138 | takeWhileTrue = undefined 139 | dropOne = undefined 140 | dropAll = undefined 141 | dropWhileFalse = undefined 142 | dropWhileTrue = undefined 143 | 144 | ------------------------------------------------------------------------------- 145 | -- Iteration 146 | ------------------------------------------------------------------------------- 147 | 148 | iterStreamLen, maxIters :: Int 149 | iterStreamLen = 10 150 | maxIters = 100000 151 | 152 | {-# INLINE iterateSource #-} 153 | iterateSource :: (Stream Int -> Stream Int) -> Int -> Int -> Stream Int 154 | iterateSource g i n = f i (sourceN iterStreamLen n) 155 | where 156 | f (0 :: Int) m = g m 157 | f x m = g (f (x P.- 1) m) 158 | 159 | {-# INLINE iterateScan #-} 160 | {-# INLINE iterateFilterEven #-} 161 | {-# INLINE iterateTakeAll #-} 162 | {-# INLINE iterateDropOne #-} 163 | {-# INLINE iterateDropWhileFalse #-} 164 | {-# INLINE iterateDropWhileTrue #-} 165 | iterateScan, iterateFilterEven, iterateTakeAll, iterateDropOne, 166 | iterateDropWhileFalse, iterateDropWhileTrue :: Int -> Stream Int 167 | 168 | -- this is quadratic 169 | iterateScan = undefined 170 | iterateDropWhileFalse = undefined 171 | 172 | iterateFilterEven = undefined 173 | iterateTakeAll = undefined 174 | iterateDropOne = undefined 175 | iterateDropWhileTrue = undefined 176 | 177 | ------------------------------------------------------------------------------- 178 | -- Mixed Composition 179 | ------------------------------------------------------------------------------- 180 | 181 | {-# INLINE scanMap #-} 182 | {-# INLINE dropMap #-} 183 | {-# INLINE dropScan #-} 184 | {-# INLINE takeDrop #-} 185 | {-# INLINE takeScan #-} 186 | {-# INLINE takeMap #-} 187 | {-# INLINE filterDrop #-} 188 | {-# INLINE filterTake #-} 189 | {-# INLINE filterScan #-} 190 | {-# INLINE filterMap #-} 191 | scanMap, dropMap, dropScan, takeDrop, takeScan, takeMap, filterDrop, 192 | filterTake, filterScan, filterMap 193 | :: Int -> Stream Int -> () 194 | 195 | scanMap = undefined 196 | dropMap = undefined 197 | dropScan = undefined 198 | takeDrop = undefined 199 | takeScan = undefined 200 | takeMap = undefined 201 | filterDrop = undefined 202 | filterTake = undefined 203 | filterScan = undefined 204 | filterMap = undefined 205 | 206 | ------------------------------------------------------------------------------- 207 | -- Zipping and concat 208 | ------------------------------------------------------------------------------- 209 | 210 | {-# INLINE zip #-} 211 | zip :: Stream Int -> () 212 | zip _src = undefined 213 | 214 | {-# INLINE concat #-} 215 | concat :: Stream Int -> () 216 | concat _src = undefined 217 | 218 | main :: P.IO () 219 | main = $(defaultMain "DList") 220 | -------------------------------------------------------------------------------- /Benchmarks/Drinkery.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE RankNTypes #-} 2 | {-# LANGUAGE TemplateHaskell #-} 3 | 4 | module Benchmarks.Drinkery where 5 | 6 | import Benchmarks.DefaultMain (defaultMain) 7 | import Benchmarks.Common (value, maxValue) 8 | import Control.Monad (void) 9 | import Prelude 10 | (Monad, Int, Maybe(..), (+), ($), return, even, (>), (<=), 11 | subtract, undefined, replicate, (<$>), (<*>), fst, id, const, 12 | maxBound, IO) 13 | 14 | import qualified Data.Drinkery as S 15 | import qualified Data.Drinkery.Finite as S 16 | 17 | ------------------------------------------------------------------------------- 18 | -- Stream generation and elimination 19 | ------------------------------------------------------------------------------- 20 | 21 | type Source m i o = S.Source () o m 22 | type Pipe m i o = S.Pipe i o m 23 | type Sink m a r = S.Sink (S.Source () a) m r 24 | 25 | {-# INLINE source #-} 26 | source :: Monad m => Int -> Source m () Int 27 | source n = S.unfoldrTapM 28 | (const $ \x -> return (if x > n + value then Nothing else Just x, x + 1)) n 29 | 30 | ------------------------------------------------------------------------------- 31 | -- Elimination 32 | ------------------------------------------------------------------------------- 33 | 34 | {-# INLINE runStream #-} 35 | runStream :: Monad m => Pipe m Int o -> Source m () Int -> m () 36 | runStream t src = void $ src S.++& t S.$& S.drainFrom S.consume 37 | 38 | {-# INLINE eliminate #-} 39 | eliminate :: Monad m => Sink m Int a -> Source m () Int -> m () 40 | eliminate s src = void $ src S.++& s 41 | 42 | {-# INLINE toNull #-} 43 | {-# INLINE toList #-} 44 | {-# INLINE foldl #-} 45 | {-# INLINE last #-} 46 | toNull, toList, foldl, last :: Monad m => Source m () Int -> m () 47 | toNull = eliminate $ S.drainFrom S.consume 48 | toList = eliminate S.drinkUp 49 | foldl = eliminate $ S.foldlFrom' S.consume (+) 0 50 | last = eliminate $ S.lastFrom S.consume 51 | 52 | ------------------------------------------------------------------------------- 53 | -- Transformation 54 | ------------------------------------------------------------------------------- 55 | 56 | {-# INLINE transform #-} 57 | transform :: Monad m => Pipe m Int o -> Source m () Int -> m () 58 | transform = runStream 59 | 60 | {-# INLINE composeN #-} 61 | composeN 62 | :: Monad m 63 | => Int 64 | -> (forall n. Monad n => Pipe n Int Int) 65 | -> Source m () Int 66 | -> m () 67 | composeN n f = 68 | case n of 69 | 1 -> transform $ f 70 | 2 -> transform $ f S.++$ f 71 | 3 -> transform $ f S.++$ f S.++$ f 72 | 4 -> transform $ f S.++$ f S.++$ f S.++$ f 73 | _ -> undefined 74 | 75 | {-# INLINE scan #-} 76 | {-# INLINE map #-} 77 | {-# INLINE mapM #-} 78 | {-# INLINE filterEven #-} 79 | {-# INLINE filterAllOut #-} 80 | {-# INLINE filterAllIn #-} 81 | {-# INLINE takeOne #-} 82 | {-# INLINE takeAll #-} 83 | {-# INLINE takeWhileTrue #-} 84 | {-# INLINE dropOne #-} 85 | {-# INLINE dropAll #-} 86 | {-# INLINE dropWhileTrue #-} 87 | {-# INLINE dropWhileFalse #-} 88 | scan, map, mapM, 89 | filterEven, filterAllOut, filterAllIn, 90 | takeOne, takeAll, takeWhileTrue, 91 | dropOne, dropAll, dropWhileTrue, dropWhileFalse 92 | :: Monad m => Int -> Source m () Int -> m () 93 | 94 | scan n = composeN n $ S.scan (+) 0 95 | map n = composeN n $ S.map (+1) 96 | mapM n = composeN n $ S.traverse return 97 | filterEven n = composeN n $ S.filter even 98 | filterAllOut n = composeN n $ S.filter (> maxValue) 99 | filterAllIn n = composeN n $ S.filter (<= maxValue) 100 | takeOne n = composeN n $ S.take 1 101 | takeAll n = composeN n $ S.take maxValue 102 | takeWhileTrue n = composeN n $ S.takeWhile (<= maxValue) 103 | dropOne n = composeN n $ S.drop 1 104 | dropAll n = composeN n $ S.drop maxValue 105 | dropWhileFalse n = composeN n $ S.dropWhile (> maxValue) 106 | dropWhileTrue n = composeN n $ S.dropWhile (<= maxValue) 107 | 108 | ------------------------------------------------------------------------------- 109 | -- Mixed Composition 110 | ------------------------------------------------------------------------------- 111 | 112 | {-# INLINE scanMap #-} 113 | {-# INLINE dropMap #-} 114 | {-# INLINE dropScan #-} 115 | {-# INLINE takeDrop #-} 116 | {-# INLINE takeScan #-} 117 | {-# INLINE takeMap #-} 118 | {-# INLINE filterDrop #-} 119 | {-# INLINE filterTake #-} 120 | {-# INLINE filterScan #-} 121 | {-# INLINE filterMap #-} 122 | scanMap, dropMap, dropScan, takeDrop, takeScan, takeMap, filterDrop, 123 | filterTake, filterScan, filterMap 124 | :: Monad m => Int -> Source m () Int -> m () 125 | 126 | scanMap n = composeN n $ S.map (subtract 1) S.++$ S.scan (+) 0 127 | dropMap n = composeN n $ S.map (subtract 1) S.++$ S.drop 1 128 | dropScan n = composeN n $ S.scan (+) 0 S.++$ S.drop 1 129 | takeDrop n = composeN n $ S.drop 1 S.++$ S.take maxValue 130 | takeScan n = composeN n $ S.scan (+) 0 S.++$ S.take maxValue 131 | takeMap n = composeN n $ S.map (subtract 1) S.++$ S.take maxValue 132 | filterDrop n = composeN n $ S.drop 1 S.++$ S.filter (<= maxValue) 133 | filterTake n = composeN n $ S.take maxValue S.++$ S.filter (<= maxValue) 134 | filterScan n = composeN n $ S.scan (+) 0 S.++$ S.filter (<= maxBound) 135 | filterMap n = composeN n $ S.map (subtract 1) S.++$ S.filter (<= maxValue) 136 | 137 | ------------------------------------------------------------------------------- 138 | -- Zipping and concat 139 | ------------------------------------------------------------------------------- 140 | 141 | {-# INLINE zip #-} 142 | zip :: Monad m => Source m () Int -> m () 143 | 144 | zip src = void 145 | $ S.unJoint ((,) <$> S.Joint src <*> S.Joint src) 146 | S.++& S.drainFrom (fst <$> S.consume) 147 | 148 | {-# INLINE concatMapFoldable #-} 149 | concatMapFoldable :: Monad m => Source m () Int -> m () 150 | concatMapFoldable = transform $ S.map (replicate 3) S.++$ S.concatMap id 151 | 152 | main :: IO () 153 | main = $(defaultMain "Drinkery") 154 | -------------------------------------------------------------------------------- /Benchmarks/List.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : Benchmarks.List 3 | -- Copyright : (c) 2018 Harendra Kumar 4 | -- 5 | -- License : MIT 6 | -- Maintainer : harendra.kumar@gmail.com 7 | 8 | {-# LANGUAGE ScopedTypeVariables #-} 9 | {-# LANGUAGE TemplateHaskell #-} 10 | 11 | module Benchmarks.List where 12 | 13 | import Benchmarks.DefaultMain (defaultMain) 14 | import Benchmarks.Common (value, maxValue, appendValue) 15 | import Prelude (Int, (+), ($), (.), even, (>), (<=), subtract, undefined, 16 | maxBound, Maybe(..)) 17 | import qualified Prelude as P 18 | 19 | import qualified Data.List as S 20 | 21 | ------------------------------------------------------------------------------- 22 | -- Stream generation and elimination 23 | ------------------------------------------------------------------------------- 24 | 25 | type Stream = [] 26 | 27 | {-# INLINE source #-} 28 | source :: Int -> [Int] 29 | -- source v = [v..v+value] 30 | 31 | source n = S.unfoldr step n 32 | where 33 | step cnt = 34 | if cnt > n + value 35 | then Nothing 36 | else (Just (cnt, cnt + 1)) 37 | 38 | {-# INLINE sourceN #-} 39 | sourceN :: Int -> Int -> Stream Int 40 | sourceN count begin = S.unfoldr step begin 41 | where 42 | step i = 43 | if i > begin + count 44 | then Nothing 45 | else (Just (i, i + 1)) 46 | 47 | {-# INLINE sourceIntFromThenTo #-} 48 | sourceIntFromThenTo :: Int -> Stream Int 49 | sourceIntFromThenTo n = P.enumFromThenTo n (n + 1) (n + value) 50 | 51 | ------------------------------------------------------------------------------- 52 | -- Append 53 | ------------------------------------------------------------------------------- 54 | 55 | {-# INLINE appendSourceR #-} 56 | appendSourceR :: Int -> () 57 | appendSourceR n = 58 | toNull $ P.foldr (S.++) [] (P.map (: []) [n..n+appendValue]) 59 | 60 | {-# INLINE appendSourceL #-} 61 | appendSourceL :: Int -> () 62 | appendSourceL n = 63 | toNull $ P.foldl (S.++) [] (P.map (: []) [n..n+appendValue]) 64 | 65 | ------------------------------------------------------------------------------- 66 | -- Elimination 67 | ------------------------------------------------------------------------------- 68 | 69 | -- Using NFData for evaluation may be fraught with problems because of a 70 | -- non-optimal implementation of NFData instance. So we just evaluate each 71 | -- element of the stream using a fold. 72 | {-# INLINE eval #-} 73 | eval :: Stream a -> () 74 | eval = S.foldr P.seq () 75 | 76 | {-# INLINE toNull #-} 77 | {-# INLINE toList #-} 78 | toNull, toList :: Stream Int -> () 79 | 80 | toNull = eval 81 | toList = eval 82 | 83 | {-# INLINE foldl #-} 84 | {-# INLINE last #-} 85 | foldl, last :: Stream Int -> Int 86 | foldl = S.foldl' (+) 0 87 | last = S.last 88 | 89 | ------------------------------------------------------------------------------- 90 | -- Transformation 91 | ------------------------------------------------------------------------------- 92 | 93 | {-# INLINE transform #-} 94 | transform :: Stream a -> () 95 | transform = eval 96 | 97 | {-# INLINE composeN #-} 98 | composeN :: Int -> (Stream Int -> Stream Int) -> Stream Int -> () 99 | composeN n f = 100 | case n of 101 | 1 -> transform . f 102 | 2 -> transform . f . f 103 | 3 -> transform . f . f . f 104 | 4 -> transform . f . f . f . f 105 | _ -> undefined 106 | 107 | {-# INLINE scan #-} 108 | {-# INLINE map #-} 109 | {-# INLINE mapM #-} 110 | {-# INLINE filterEven #-} 111 | {-# INLINE filterAllOut #-} 112 | {-# INLINE filterAllIn #-} 113 | {-# INLINE takeOne #-} 114 | {-# INLINE takeAll #-} 115 | {-# INLINE takeWhileTrue #-} 116 | {-# INLINE dropOne #-} 117 | {-# INLINE dropAll #-} 118 | {-# INLINE dropWhileTrue #-} 119 | {-# INLINE dropWhileFalse #-} 120 | scan, map, mapM, 121 | filterEven, filterAllOut, filterAllIn, 122 | takeOne, takeAll, takeWhileTrue, 123 | dropOne, dropAll, dropWhileTrue, dropWhileFalse 124 | :: Int -> Stream Int -> () 125 | 126 | scan n = composeN n $ S.scanl' (+) 0 127 | map n = composeN n $ S.map (+1) 128 | mapM = map 129 | filterEven n = composeN n $ S.filter even 130 | filterAllOut n = composeN n $ S.filter (> maxValue) 131 | filterAllIn n = composeN n $ S.filter (<= maxValue) 132 | takeOne n = composeN n $ S.take 1 133 | takeAll n = composeN n $ S.take maxValue 134 | takeWhileTrue n = composeN n $ S.takeWhile (<= maxValue) 135 | dropOne n = composeN n $ S.drop 1 136 | dropAll n = composeN n $ S.drop maxValue 137 | dropWhileFalse n = composeN n $ S.dropWhile (> maxValue) 138 | dropWhileTrue n = composeN n $ S.dropWhile (<= maxValue) 139 | 140 | ------------------------------------------------------------------------------- 141 | -- Iteration 142 | ------------------------------------------------------------------------------- 143 | 144 | iterStreamLen, maxIters :: Int 145 | iterStreamLen = 10 146 | maxIters = 100000 147 | 148 | {-# INLINE iterateSource #-} 149 | iterateSource :: (Stream Int -> Stream Int) -> Int -> Int -> Stream Int 150 | iterateSource g i n = f i (sourceN iterStreamLen n) 151 | where 152 | f (0 :: Int) m = g m 153 | f x m = g (f (x P.- 1) m) 154 | 155 | {-# INLINE iterateScan #-} 156 | {-# INLINE iterateFilterEven #-} 157 | {-# INLINE iterateTakeAll #-} 158 | {-# INLINE iterateDropOne #-} 159 | {-# INLINE iterateDropWhileFalse #-} 160 | {-# INLINE iterateDropWhileTrue #-} 161 | iterateScan, iterateFilterEven, iterateTakeAll, iterateDropOne, 162 | iterateDropWhileFalse, iterateDropWhileTrue :: Int -> Stream Int 163 | 164 | -- Scan increases the size of the stream by 1, drop 1 to not blow up the size 165 | -- due to many iterations. 166 | iterateScan n = iterateSource (S.drop 1 . S.scanl' (+) 0) maxIters n 167 | iterateFilterEven n = iterateSource (S.filter even) maxIters n 168 | iterateTakeAll n = iterateSource (S.take maxValue) maxIters n 169 | iterateDropOne n = iterateSource (S.drop 1) maxIters n 170 | iterateDropWhileFalse n = iterateSource (S.dropWhile (> maxValue)) maxIters n 171 | iterateDropWhileTrue n = iterateSource (S.dropWhile (<= maxValue)) maxIters n 172 | 173 | ------------------------------------------------------------------------------- 174 | -- Mixed Composition 175 | ------------------------------------------------------------------------------- 176 | 177 | {-# INLINE scanMap #-} 178 | {-# INLINE dropMap #-} 179 | {-# INLINE dropScan #-} 180 | {-# INLINE takeDrop #-} 181 | {-# INLINE takeScan #-} 182 | {-# INLINE takeMap #-} 183 | {-# INLINE filterDrop #-} 184 | {-# INLINE filterTake #-} 185 | {-# INLINE filterScan #-} 186 | {-# INLINE filterMap #-} 187 | scanMap, dropMap, dropScan, takeDrop, takeScan, takeMap, filterDrop, 188 | filterTake, filterScan, filterMap 189 | :: Int -> Stream Int -> () 190 | 191 | scanMap n = composeN n $ S.map (subtract 1) . S.scanl' (+) 0 192 | dropMap n = composeN n $ S.map (subtract 1) . S.drop 1 193 | dropScan n = composeN n $ S.scanl' (+) 0 . S.drop 1 194 | takeDrop n = composeN n $ S.drop 1 . S.take maxValue 195 | takeScan n = composeN n $ S.scanl' (+) 0 . S.take maxValue 196 | takeMap n = composeN n $ S.map (subtract 1) . S.take maxValue 197 | filterDrop n = composeN n $ S.drop 1 . S.filter (<= maxValue) 198 | filterTake n = composeN n $ S.take maxValue . S.filter (<= maxValue) 199 | filterScan n = composeN n $ S.scanl' (+) 0 . S.filter (<= maxBound) 200 | filterMap n = composeN n $ S.map (subtract 1) . S.filter (<= maxValue) 201 | 202 | ------------------------------------------------------------------------------- 203 | -- Zipping and concat 204 | ------------------------------------------------------------------------------- 205 | 206 | {-# INLINE zip #-} 207 | zip :: Stream Int -> () 208 | zip src = P.foldr (\(x,y) xs -> P.seq x (P.seq y xs)) () 209 | $ S.zipWith (,) src src 210 | 211 | {-# INLINE concatMap #-} 212 | concatMap :: Stream Int -> () 213 | concatMap src = transform $ (S.concatMap (S.replicate 3) src) 214 | 215 | main :: P.IO () 216 | main = $(defaultMain "List") 217 | -------------------------------------------------------------------------------- /Benchmarks/ListT.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : Benchmarks.ListT 3 | -- Copyright : (c) 2018 Harendra Kumar 4 | -- 5 | -- License : MIT 6 | -- Maintainer : harendra.kumar@gmail.com 7 | 8 | module Benchmarks.ListT where 9 | 10 | import Benchmarks.Common (value, maxValue) 11 | import Control.Monad (void, (>=>)) 12 | import Control.Monad.Trans.Class (lift) 13 | import Prelude 14 | (Monad, Int, (+), id, ($), (.), return, fmap, even, (>), (<=), 15 | (>>=), subtract, undefined) 16 | 17 | import qualified ListT as S 18 | 19 | ------------------------------------------------------------------------------- 20 | -- Benchmark ops 21 | ------------------------------------------------------------------------------- 22 | 23 | toNull, toList, foldl, last, scan, map, filterEven, mapM, filterAllOut, 24 | filterAllIn, takeOne, takeAll, takeWhileTrue, dropAll, dropWhileTrue, zip, 25 | concat, composeMapM, composeAllInFilters, composeAllOutFilters, 26 | composeMapAllInFilter, composeDropOne 27 | :: Monad m 28 | => Int -> m () 29 | 30 | ------------------------------------------------------------------------------- 31 | -- Stream generation and elimination 32 | ------------------------------------------------------------------------------- 33 | 34 | type Stream m a = S.ListT m a 35 | 36 | source :: Monad m => Int -> Stream m Int 37 | source n = S.fromFoldable [n..n+value] 38 | 39 | runStream :: Monad m => Stream m a -> m () 40 | runStream = S.traverse_ (\_ -> return ()) 41 | 42 | ------------------------------------------------------------------------------- 43 | -- Elimination 44 | ------------------------------------------------------------------------------- 45 | 46 | eliminate :: Monad m => (Stream m Int -> m a) -> Int -> m () 47 | eliminate f = void . f . source 48 | 49 | toNull = eliminate $ runStream 50 | toList = eliminate $ S.toList 51 | foldl = eliminate $ undefined 52 | last = eliminate $ undefined 53 | 54 | ------------------------------------------------------------------------------- 55 | -- Transformation 56 | ------------------------------------------------------------------------------- 57 | 58 | transform :: Monad m => (Int -> Stream m Int) -> Int -> m () 59 | transform f n = runStream (source n >>= f) 60 | 61 | scan = transform $ undefined 62 | map = transform $ undefined 63 | mapM = transform $ undefined 64 | filterEven = transform $ undefined 65 | filterAllOut = transform $ undefined 66 | filterAllIn = transform $ undefined 67 | takeOne = transform $ undefined 68 | takeAll = transform $ undefined 69 | takeWhileTrue = transform $ undefined 70 | dropAll = transform $ undefined 71 | dropWhileTrue = transform $ undefined 72 | 73 | ------------------------------------------------------------------------------- 74 | -- Zipping and concat 75 | ------------------------------------------------------------------------------- 76 | 77 | zip n = undefined 78 | concat _n = undefined 79 | 80 | ------------------------------------------------------------------------------- 81 | -- Composition 82 | ------------------------------------------------------------------------------- 83 | 84 | compose :: Monad m => (Int -> Stream m Int) -> Int -> m () 85 | compose f = transform $ (f >=> f >=> f >=> f) 86 | 87 | composeMapM = compose (lift . return) 88 | composeAllInFilters = compose undefined 89 | composeAllOutFilters = compose undefined 90 | composeMapAllInFilter = compose undefined 91 | composeDropOne = compose undefined 92 | 93 | composeScaling :: Monad m => Int -> Int -> m () 94 | composeScaling m n = 95 | case m of 96 | 1 -> transform f n 97 | 2 -> transform (f >=> f) n 98 | 3 -> transform (f >=> f >=> f) n 99 | 4 -> transform (f >=> f >=> f >=> f) n 100 | _ -> undefined 101 | where f = undefined 102 | -------------------------------------------------------------------------------- /Benchmarks/ListTransformer.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : Benchmarks.ListTransformer 3 | -- Copyright : (c) 2018 Harendra Kumar 4 | -- 5 | -- License : MIT 6 | -- Maintainer : harendra.kumar@gmail.com 7 | 8 | module Benchmarks.ListTransformer where 9 | 10 | import Benchmarks.Common (value, maxValue) 11 | import Control.Monad (void, (>=>)) 12 | import Control.Monad.Trans.Class (lift) 13 | import Prelude 14 | (Monad, Int, (+), id, ($), (.), return, fmap, even, (>), (<=), 15 | (>>=), subtract, undefined) 16 | 17 | import qualified List.Transformer as S 18 | 19 | ------------------------------------------------------------------------------- 20 | -- Benchmark ops 21 | ------------------------------------------------------------------------------- 22 | 23 | toNull, toList, foldl, last, scan, map, filterEven, mapM, filterAllOut, 24 | filterAllIn, takeOne, takeAll, takeWhileTrue, dropAll, dropWhileTrue, zip, 25 | concat, composeMapM, composeAllInFilters, composeAllOutFilters, 26 | composeMapAllInFilter, composeDropOne 27 | :: Monad m 28 | => Int -> m () 29 | 30 | ------------------------------------------------------------------------------- 31 | -- Stream generation and elimination 32 | ------------------------------------------------------------------------------- 33 | 34 | type Stream m a = S.ListT m a 35 | 36 | source :: Monad m => Int -> Stream m Int 37 | source n = S.select [n..n+value] 38 | 39 | runStream :: Monad m => Stream m a -> m () 40 | runStream = S.runListT 41 | 42 | ------------------------------------------------------------------------------- 43 | -- Elimination 44 | ------------------------------------------------------------------------------- 45 | 46 | eliminate :: Monad m => (Stream m Int -> m a) -> Int -> m () 47 | eliminate f = void . f . source 48 | 49 | toNull = eliminate $ runStream 50 | toList = eliminate $ undefined 51 | foldl = eliminate $ S.fold (+) 0 id 52 | last = eliminate $ undefined 53 | 54 | ------------------------------------------------------------------------------- 55 | -- Transformation 56 | ------------------------------------------------------------------------------- 57 | 58 | transform :: Monad m => (Int -> Stream m Int) -> Int -> m () 59 | transform f n = runStream (source n >>= f) 60 | 61 | scan = transform $ undefined 62 | map = transform $ (lift . return . (+1)) 63 | mapM = transform $ undefined 64 | filterEven = transform $ undefined 65 | filterAllOut = transform $ undefined 66 | filterAllIn = transform $ undefined 67 | takeOne = transform $ undefined 68 | takeAll = transform $ undefined 69 | takeWhileTrue = transform $ undefined 70 | dropAll = transform $ undefined 71 | dropWhileTrue = transform $ undefined 72 | 73 | ------------------------------------------------------------------------------- 74 | -- Zipping and concat 75 | ------------------------------------------------------------------------------- 76 | 77 | zip n = undefined 78 | concat _n = undefined 79 | 80 | ------------------------------------------------------------------------------- 81 | -- Composition 82 | ------------------------------------------------------------------------------- 83 | 84 | compose :: Monad m => (Int -> Stream m Int) -> Int -> m () 85 | compose f = transform $ (f >=> f >=> f >=> f) 86 | 87 | composeMapM = compose (lift . return) 88 | composeAllInFilters = compose undefined 89 | composeAllOutFilters = compose undefined 90 | composeMapAllInFilter = compose undefined 91 | composeDropOne = compose undefined 92 | 93 | composeScaling :: Monad m => Int -> Int -> m () 94 | composeScaling m n = 95 | case m of 96 | 1 -> transform f n 97 | 2 -> transform (f >=> f) n 98 | 3 -> transform (f >=> f >=> f) n 99 | 4 -> transform (f >=> f >=> f >=> f) n 100 | _ -> undefined 101 | where f = undefined 102 | -------------------------------------------------------------------------------- /Benchmarks/LogicT.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : Benchmarks.LogicT 3 | -- Copyright : (c) 2018 Harendra Kumar 4 | -- 5 | -- License : MIT 6 | -- Maintainer : harendra.kumar@gmail.com 7 | 8 | module Benchmarks.LogicT where 9 | 10 | import Benchmarks.Common (value, maxValue) 11 | import Control.Monad (void, (>=>)) 12 | import Prelude 13 | (Monad, Int, (+), id, ($), (.), return, fmap, even, (>), (<=), 14 | (>>=), subtract, undefined) 15 | 16 | import qualified Control.Monad.Logic as S 17 | import qualified Data.List as List 18 | 19 | ------------------------------------------------------------------------------- 20 | -- Benchmark ops 21 | ------------------------------------------------------------------------------- 22 | 23 | toNull, toList, foldl, last, scan, map, filterEven, mapM, filterAllOut, 24 | filterAllIn, takeOne, takeAll, takeWhileTrue, dropAll, dropWhileTrue, zip, 25 | concat, composeMapM, composeAllInFilters, composeAllOutFilters, 26 | composeMapAllInFilter, composeDropOne 27 | :: Monad m 28 | => Int -> m () 29 | 30 | ------------------------------------------------------------------------------- 31 | -- Stream generation and elimination 32 | ------------------------------------------------------------------------------- 33 | 34 | type Stream m a = S.LogicT m a 35 | 36 | source :: Int -> Stream m Int 37 | source n = S.msum $ List.map return [n..n+value] 38 | 39 | runStream :: Monad m => Stream m a -> m () 40 | runStream = void . S.observeAllT -- this is actually a toList equivalent 41 | 42 | ------------------------------------------------------------------------------- 43 | -- Elimination 44 | ------------------------------------------------------------------------------- 45 | 46 | eliminate :: Monad m => (Stream m Int -> m a) -> Int -> m () 47 | eliminate f = void . f . source 48 | 49 | toNull = eliminate $ runStream 50 | toList = eliminate $ S.observeAllT 51 | foldl = eliminate $ undefined 52 | last = eliminate $ undefined 53 | 54 | ------------------------------------------------------------------------------- 55 | -- Transformation 56 | ------------------------------------------------------------------------------- 57 | 58 | transform :: Monad m => (Int -> Stream m Int) -> Int -> m () 59 | transform f n = runStream (source n >>= f) 60 | 61 | scan = transform $ undefined 62 | map = transform $ undefined 63 | mapM = transform $ undefined 64 | filterEven = transform $ undefined 65 | filterAllOut = transform $ undefined 66 | filterAllIn = transform $ undefined 67 | takeOne = transform $ undefined 68 | takeAll = transform $ undefined 69 | takeWhileTrue = transform $ undefined 70 | dropAll = transform $ undefined 71 | dropWhileTrue = transform $ undefined 72 | 73 | ------------------------------------------------------------------------------- 74 | -- Zipping and concat 75 | ------------------------------------------------------------------------------- 76 | 77 | zip n = undefined 78 | concat _n = undefined 79 | 80 | ------------------------------------------------------------------------------- 81 | -- Composition 82 | ------------------------------------------------------------------------------- 83 | 84 | compose :: Monad m => (Int -> Stream m Int) -> Int -> m () 85 | compose f = transform $ (f >=> f >=> f >=> f) 86 | 87 | composeMapM = compose (S.lift . return) 88 | composeAllInFilters = compose undefined 89 | composeAllOutFilters = compose undefined 90 | composeMapAllInFilter = compose undefined 91 | composeDropOne = compose undefined 92 | 93 | composeScaling :: Monad m => Int -> Int -> m () 94 | composeScaling m n = 95 | case m of 96 | 1 -> transform f n 97 | 2 -> transform (f >=> f) n 98 | 3 -> transform (f >=> f >=> f) n 99 | 4 -> transform (f >=> f >=> f >=> f) n 100 | _ -> undefined 101 | where f = undefined 102 | -------------------------------------------------------------------------------- /Benchmarks/Machines.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : Benchmarks.Machines 3 | -- Copyright : (c) 2018 Harendra Kumar 4 | -- 5 | -- License : MIT 6 | -- Maintainer : harendra.kumar@gmail.com 7 | 8 | {-# LANGUAGE RankNTypes #-} 9 | {-# LANGUAGE TemplateHaskell #-} 10 | {-# OPTIONS_GHC -Wno-incomplete-patterns #-} 11 | 12 | module Benchmarks.Machines where 13 | 14 | import Benchmarks.DefaultMain (defaultMain) 15 | import Benchmarks.Common (value, maxValue, appendValue) 16 | import Prelude 17 | (Monad, Int, (.), (+), ($), return, even, (>), (<=), 18 | subtract, replicate, Maybe(..), maxBound, foldMap) 19 | import qualified Prelude as P 20 | import Data.Semigroup ((<>)) 21 | 22 | import qualified Data.Machine as S 23 | 24 | ------------------------------------------------------------------------------- 25 | -- Stream generation and elimination 26 | ------------------------------------------------------------------------------- 27 | 28 | type Source m o = S.SourceT m o 29 | type Pipe m i o = S.ProcessT m i o 30 | 31 | source :: Monad m => Int -> Source m Int 32 | -- source n = S.source [n..n+value] 33 | source n = S.unfoldT step n 34 | where 35 | step cnt = 36 | if cnt > n + value 37 | then return Nothing 38 | else return (Just (cnt, cnt + 1)) 39 | 40 | ------------------------------------------------------------------------------- 41 | -- Append 42 | ------------------------------------------------------------------------------- 43 | 44 | {-# INLINE appendSourceR #-} 45 | appendSourceR :: Int -> P.IO () 46 | appendSourceR n = 47 | toNull $ foldMap (S.construct . S.yield) [n..n+appendValue] 48 | 49 | -- XXX use S.prepended instead? 50 | {-# INLINE appendSourceL #-} 51 | appendSourceL :: Int -> P.IO () 52 | appendSourceL n = 53 | toNull $ P.foldl (<>) P.mempty (P.map (S.construct . S.yield) [n..n+appendValue]) 54 | 55 | ------------------------------------------------------------------------------- 56 | -- Elimination 57 | ------------------------------------------------------------------------------- 58 | 59 | {-# INLINE runStream #-} 60 | runStream :: Monad m => Pipe m Int o -> S.MachineT m k Int -> m () 61 | runStream t src = S.runT_ $ src S.~> t 62 | 63 | {-# INLINE toNull #-} 64 | {-# INLINE toList #-} 65 | {-# INLINE foldl #-} 66 | {-# INLINE last #-} 67 | toNull, foldl, last :: Monad m => S.MachineT m k Int -> m () 68 | toList :: Monad m => S.MachineT m k Int -> m [Int] 69 | 70 | toNull = S.runT_ 71 | toList = S.runT 72 | foldl = runStream $ S.fold (+) 0 73 | last = runStream $ S.final 74 | 75 | ------------------------------------------------------------------------------- 76 | -- Transformation 77 | ------------------------------------------------------------------------------- 78 | 79 | {-# INLINE transform #-} 80 | transform :: Monad m => Pipe m Int o -> S.MachineT m k Int -> m () 81 | transform = runStream 82 | 83 | {-# INLINE composeN #-} 84 | composeN :: Monad m => Int -> Pipe m Int Int -> S.MachineT m k Int -> m () 85 | composeN n f = 86 | case n of 87 | 1 -> transform $ f 88 | 2 -> transform $ f S.~> f 89 | 3 -> transform $ f S.~> f S.~> f 90 | 4 -> transform $ f S.~> f S.~> f S.~> f 91 | -- _ -> undefined 92 | 93 | {-# INLINE scan #-} 94 | {-# INLINE map #-} 95 | {-# INLINE mapM #-} 96 | {-# INLINE filterEven #-} 97 | {-# INLINE filterAllOut #-} 98 | {-# INLINE filterAllIn #-} 99 | {-# INLINE takeOne #-} 100 | {-# INLINE takeAll #-} 101 | {-# INLINE takeWhileTrue #-} 102 | {-# INLINE dropOne #-} 103 | {-# INLINE dropAll #-} 104 | {-# INLINE dropWhileTrue #-} 105 | {-# INLINE dropWhileFalse #-} 106 | scan, map, mapM, 107 | filterEven, filterAllOut, filterAllIn, 108 | takeOne, takeAll, takeWhileTrue, 109 | dropOne, dropAll, dropWhileTrue, dropWhileFalse 110 | :: Monad m => Int -> S.MachineT m k Int -> m () 111 | 112 | scan n = composeN n $ S.scan (+) 0 113 | map n = composeN n $ S.mapping (+1) 114 | mapM n = composeN n $ S.autoM return 115 | filterEven n = composeN n $ S.filtered even 116 | filterAllOut n = composeN n $ S.filtered (> maxValue) 117 | filterAllIn n = composeN n $ S.filtered (<= maxValue) 118 | takeOne n = composeN n $ S.taking 1 119 | takeAll n = composeN n $ S.taking maxValue 120 | takeWhileTrue n = composeN n $ S.takingWhile (<= maxValue) 121 | dropOne n = composeN n $ S.dropping 1 122 | dropAll n = composeN n $ S.dropping maxValue 123 | dropWhileFalse n = composeN n $ S.droppingWhile (> maxValue) 124 | dropWhileTrue n = composeN n $ S.droppingWhile (<= maxValue) 125 | 126 | ------------------------------------------------------------------------------- 127 | -- Mixed Composition 128 | ------------------------------------------------------------------------------- 129 | 130 | {-# INLINE scanMap #-} 131 | {-# INLINE dropMap #-} 132 | {-# INLINE dropScan #-} 133 | {-# INLINE takeDrop #-} 134 | {-# INLINE takeScan #-} 135 | {-# INLINE takeMap #-} 136 | {-# INLINE filterDrop #-} 137 | {-# INLINE filterTake #-} 138 | {-# INLINE filterScan #-} 139 | {-# INLINE filterMap #-} 140 | scanMap, dropMap, dropScan, takeDrop, takeScan, takeMap, filterDrop, 141 | filterTake, filterScan, filterMap 142 | :: Monad m => Int -> S.MachineT m k Int -> m () 143 | 144 | scanMap n = composeN n $ S.mapping (subtract 1) S.~> S.scan (+) 0 145 | dropMap n = composeN n $ S.mapping (subtract 1) S.~> S.dropping 1 146 | dropScan n = composeN n $ S.scan (+) 0 S.~> S.dropping 1 147 | takeDrop n = composeN n $ S.dropping 1 S.~> S.taking maxValue 148 | takeScan n = composeN n $ S.scan (+) 0 S.~> S.taking maxValue 149 | takeMap n = composeN n $ S.mapping (subtract 1) S.~> S.taking maxValue 150 | filterDrop n = composeN n $ S.dropping 1 S.~> S.filtered (<= maxValue) 151 | filterTake n = composeN n $ S.taking maxValue S.~> S.filtered (<= maxValue) 152 | filterScan n = composeN n $ S.scan (+) 0 S.~> S.filtered (<= maxBound) 153 | filterMap n = composeN n $ S.mapping (subtract 1) S.~> S.filtered (<= maxValue) 154 | 155 | ------------------------------------------------------------------------------- 156 | -- Zipping and concat 157 | ------------------------------------------------------------------------------- 158 | 159 | {-# INLINE zip #-} 160 | zip :: Monad m => S.MachineT m k Int -> m () 161 | 162 | zip _src = S.runT_ (S.capT (source 10) (source 20) S.zipping) 163 | 164 | {-# INLINE concatMapFoldable #-} 165 | concatMapFoldable :: Monad m => S.MachineT m k Int -> m () 166 | concatMapFoldable = transform (S.mapping (replicate 3) S.~> S.asParts) 167 | 168 | main :: P.IO () 169 | main = $(defaultMain "Machines") 170 | -------------------------------------------------------------------------------- /Benchmarks/Pipes.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : Benchmarks.Pipes 3 | -- Copyright : (c) 2018 Harendra Kumar 4 | -- 5 | -- License : MIT 6 | -- Maintainer : harendra.kumar@gmail.com 7 | 8 | {-# LANGUAGE RankNTypes #-} 9 | {-# LANGUAGE TemplateHaskell #-} 10 | 11 | module Benchmarks.Pipes where 12 | 13 | import Benchmarks.DefaultMain (defaultMain) 14 | import Benchmarks.Common (value, maxValue, appendValue) 15 | import Data.Void (Void) 16 | import Prelude 17 | (Monad, Int, (+), ($), id, return, even, (>), (<=), 18 | subtract, undefined, replicate, Maybe, Either(..), foldMap, maxBound) 19 | import qualified Prelude as P 20 | import Data.Semigroup ((<>)) 21 | 22 | import qualified Pipes as S 23 | import qualified Pipes.Prelude as S 24 | 25 | ------------------------------------------------------------------------------- 26 | -- Stream generation and elimination 27 | ------------------------------------------------------------------------------- 28 | 29 | type Source m i o = S.Producer o m i 30 | type Sink m i r = S.Proxy () i () Void m r 31 | type Pipe m i o = S.Proxy () i () o m () 32 | 33 | {-# INLINE source #-} 34 | source :: Monad m => Int -> Source m () Int 35 | -- source n = S.each [n..n+value] 36 | source n = S.unfoldr step n 37 | where 38 | step cnt = 39 | if cnt > n + value 40 | then return $ Left () 41 | else return (Right (cnt, cnt + 1)) 42 | 43 | ------------------------------------------------------------------------------- 44 | -- Append 45 | ------------------------------------------------------------------------------- 46 | 47 | {-# INLINE appendSourceR #-} 48 | appendSourceR :: Int -> P.IO () 49 | appendSourceR n = 50 | toNull $ foldMap S.yield [n..n+appendValue] 51 | 52 | {-# INLINE appendSourceL #-} 53 | appendSourceL :: Int -> P.IO () 54 | appendSourceL n = 55 | toNull $ P.foldl (<>) P.mempty (P.map S.yield [n..n+appendValue]) 56 | 57 | ------------------------------------------------------------------------------- 58 | -- Elimination 59 | ------------------------------------------------------------------------------- 60 | 61 | {-# INLINE toNull #-} 62 | {-# INLINE toList #-} 63 | {-# INLINE foldl #-} 64 | {-# INLINE last #-} 65 | toNull :: Monad m => Source m () Int -> m () 66 | toList :: Monad m => Source m () Int -> m [Int] 67 | foldl :: Monad m => Source m () Int -> m Int 68 | last :: Monad m => Source m () Int -> m (Maybe Int) 69 | 70 | toNull src = S.runEffect $ S.for src S.discard 71 | toList = S.toListM 72 | foldl = S.fold (+) 0 id 73 | last = S.last 74 | 75 | ------------------------------------------------------------------------------- 76 | -- Transformation 77 | ------------------------------------------------------------------------------- 78 | 79 | {-# INLINE transform #-} 80 | transform :: Monad m => Pipe m Int Int -> Source m () Int -> m () 81 | transform t src = S.runEffect $ S.for (src S.>-> t) S.discard 82 | 83 | {-# INLINE composeN #-} 84 | composeN :: Monad m => Int -> Pipe m Int Int -> Source m () Int -> m () 85 | composeN n f = 86 | case n of 87 | 1 -> transform $ f 88 | 2 -> transform $ f S.>-> f 89 | 3 -> transform $ f S.>-> f S.>-> f 90 | 4 -> transform $ f S.>-> f S.>-> f S.>-> f 91 | _ -> undefined 92 | 93 | {-# INLINE scan #-} 94 | {-# INLINE map #-} 95 | {-# INLINE mapM #-} 96 | {-# INLINE filterEven #-} 97 | {-# INLINE filterAllOut #-} 98 | {-# INLINE filterAllIn #-} 99 | {-# INLINE takeOne #-} 100 | {-# INLINE takeAll #-} 101 | {-# INLINE takeWhileTrue #-} 102 | {-# INLINE dropOne #-} 103 | {-# INLINE dropAll #-} 104 | {-# INLINE dropWhileTrue #-} 105 | {-# INLINE dropWhileFalse #-} 106 | scan, map, mapM, 107 | filterEven, filterAllOut, filterAllIn, 108 | takeOne, takeAll, takeWhileTrue, 109 | dropOne, dropAll, dropWhileTrue, dropWhileFalse 110 | :: Monad m => Int -> Source m () Int -> m () 111 | 112 | scan n = composeN n $ S.scan (+) 0 id 113 | map n = composeN n $ S.map (+1) 114 | mapM n = composeN n $ S.mapM return 115 | filterEven n = composeN n $ S.filter even 116 | filterAllOut n = composeN n $ S.filter (> maxValue) 117 | filterAllIn n = composeN n $ S.filter (<= maxValue) 118 | takeOne n = composeN n $ S.take 1 119 | takeAll n = composeN n $ S.take maxValue 120 | takeWhileTrue n = composeN n $ S.takeWhile (<= maxValue) 121 | dropOne n = composeN n $ S.drop 1 122 | dropAll n = composeN n $ S.drop maxValue 123 | dropWhileFalse n = composeN n $ S.dropWhile (> maxValue) 124 | dropWhileTrue n = composeN n $ S.dropWhile (<= maxValue) 125 | 126 | ------------------------------------------------------------------------------- 127 | -- Mixed Composition 128 | ------------------------------------------------------------------------------- 129 | 130 | {-# INLINE scanMap #-} 131 | {-# INLINE dropMap #-} 132 | {-# INLINE dropScan #-} 133 | {-# INLINE takeDrop #-} 134 | {-# INLINE takeScan #-} 135 | {-# INLINE takeMap #-} 136 | {-# INLINE filterDrop #-} 137 | {-# INLINE filterTake #-} 138 | {-# INLINE filterScan #-} 139 | {-# INLINE filterMap #-} 140 | scanMap, dropMap, dropScan, takeDrop, takeScan, takeMap, filterDrop, 141 | filterTake, filterScan, filterMap 142 | :: Monad m => Int -> Source m () Int -> m () 143 | 144 | scanMap n = composeN n $ S.map (subtract 1) S.>-> S.scan (+) 0 id 145 | dropMap n = composeN n $ S.map (subtract 1) S.>-> S.drop 1 146 | dropScan n = composeN n $ S.scan (+) 0 id S.>-> S.drop 1 147 | takeDrop n = composeN n $ S.drop 1 S.>-> S.take maxValue 148 | takeScan n = composeN n $ S.scan (+) 0 id S.>-> S.take maxValue 149 | takeMap n = composeN n $ S.map (subtract 1) S.>-> S.take maxValue 150 | filterDrop n = composeN n $ S.drop 1 S.>-> S.filter (<= maxValue) 151 | filterTake n = composeN n $ S.take maxValue S.>-> S.filter (<= maxValue) 152 | filterScan n = composeN n $ S.scan (+) 0 id S.>-> S.filter (<= maxBound) 153 | filterMap n = composeN n $ S.map (subtract 1) S.>-> S.filter (<= maxValue) 154 | 155 | ------------------------------------------------------------------------------- 156 | -- Zipping and concat 157 | ------------------------------------------------------------------------------- 158 | 159 | {-# INLINE zip #-} 160 | zip :: Monad m => Source m () Int -> m () 161 | zip src = S.runEffect $ S.for (S.zip src src) S.discard 162 | 163 | {-# INLINE concatMapFoldable #-} 164 | concatMapFoldable :: Monad m => Source m () Int -> m () 165 | concatMapFoldable = transform (S.map (replicate 3) S.>-> S.concat) 166 | 167 | main :: P.IO () 168 | main = $(defaultMain "Pipes") 169 | -------------------------------------------------------------------------------- /Benchmarks/Sequence.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : Benchmarks.Sequence 3 | -- Copyright : (c) 2018 Harendra Kumar 4 | -- 5 | -- License : MIT 6 | -- Maintainer : harendra.kumar@gmail.com 7 | 8 | {-# LANGUAGE ScopedTypeVariables #-} 9 | {-# LANGUAGE TemplateHaskell #-} 10 | 11 | module Benchmarks.Sequence where 12 | 13 | import Benchmarks.DefaultMain (defaultMain) 14 | import Benchmarks.Common (value, maxValue, appendValue) 15 | import Prelude (Int, (+), ($), (.), even, (>), (<=), subtract, undefined, 16 | Maybe(..)) 17 | import qualified Prelude as P 18 | import qualified Data.Foldable as P 19 | 20 | import qualified Data.Sequence as S 21 | 22 | ------------------------------------------------------------------------------- 23 | -- Stream generation and elimination 24 | ------------------------------------------------------------------------------- 25 | 26 | type Stream = S.Seq 27 | 28 | {-# INLINE source #-} 29 | source :: Int -> Stream Int 30 | -- source v = S.fromList [v..v+value] 31 | 32 | source n = S.unfoldr step n 33 | where 34 | step cnt = 35 | if cnt > n + value 36 | then Nothing 37 | else (Just (cnt, cnt + 1)) 38 | 39 | {-# INLINE sourceN #-} 40 | sourceN :: Int -> Int -> Stream Int 41 | sourceN count begin = S.unfoldr step begin 42 | where 43 | step i = 44 | if i > begin + count 45 | then Nothing 46 | else (Just (i, i + 1)) 47 | 48 | ------------------------------------------------------------------------------- 49 | -- Append 50 | ------------------------------------------------------------------------------- 51 | 52 | {-# INLINE appendSourceR #-} 53 | appendSourceR :: Int -> () 54 | appendSourceR n = 55 | toNull $ P.foldr (S.><) S.empty (P.map S.singleton [n..n+appendValue]) 56 | 57 | {-# INLINE appendSourceL #-} 58 | appendSourceL :: Int -> () 59 | appendSourceL n = 60 | toNull $ P.foldl (S.><) S.empty (P.map S.singleton [n..n+appendValue]) 61 | 62 | ------------------------------------------------------------------------------- 63 | -- Elimination 64 | ------------------------------------------------------------------------------- 65 | 66 | -- Using NFData for evaluation may be fraught with problems because of a 67 | -- non-optimal implementation of NFData instance. So we just evaluate each 68 | -- element of the stream using a fold. 69 | {-# INLINE eval #-} 70 | eval :: Stream a -> () 71 | eval = P.foldr P.seq () 72 | 73 | {-# INLINE toNull #-} 74 | {-# INLINE toList #-} 75 | {-# INLINE foldl #-} 76 | {-# INLINE last #-} 77 | toNull :: Stream Int -> () 78 | toList :: Stream Int -> () 79 | foldl :: Stream Int -> Int 80 | last :: Stream Int -> Int 81 | 82 | toNull = eval 83 | toList = P.foldr P.seq () . P.toList 84 | foldl = P.foldl' (+) 0 85 | last xs = 86 | case S.viewr xs of 87 | _ S.:> x -> x 88 | _ -> undefined 89 | 90 | ------------------------------------------------------------------------------- 91 | -- Transformation 92 | ------------------------------------------------------------------------------- 93 | 94 | {-# INLINE transform #-} 95 | transform :: Stream a -> () 96 | transform = eval 97 | 98 | {-# INLINE composeN #-} 99 | composeN :: Int -> (Stream Int -> Stream Int) -> Stream Int -> () 100 | composeN n f = 101 | case n of 102 | 1 -> transform . f 103 | 2 -> transform . f . f 104 | 3 -> transform . f . f . f 105 | 4 -> transform . f . f . f . f 106 | _ -> undefined 107 | 108 | {-# INLINE scan #-} 109 | {-# INLINE map #-} 110 | {-# INLINE mapM #-} 111 | {-# INLINE filterEven #-} 112 | {-# INLINE filterAllOut #-} 113 | {-# INLINE filterAllIn #-} 114 | {-# INLINE takeOne #-} 115 | {-# INLINE takeAll #-} 116 | {-# INLINE takeWhileTrue #-} 117 | {-# INLINE dropOne #-} 118 | {-# INLINE dropAll #-} 119 | {-# INLINE dropWhileTrue #-} 120 | {-# INLINE dropWhileFalse #-} 121 | scan, map, mapM, 122 | filterEven, filterAllOut, filterAllIn, 123 | takeOne, takeAll, takeWhileTrue, 124 | dropOne, dropAll, dropWhileTrue, dropWhileFalse 125 | :: Int -> Stream Int -> () 126 | 127 | scan = undefined 128 | map n = composeN n $ P.fmap (+1) 129 | mapM = map 130 | filterEven n = composeN n $ S.filter even 131 | filterAllOut n = composeN n $ S.filter (> maxValue) 132 | filterAllIn n = composeN n $ S.filter (<= maxValue) 133 | takeOne n = composeN n $ S.take 1 134 | takeAll n = composeN n $ S.take maxValue 135 | takeWhileTrue n = composeN n $ S.takeWhileL (<= maxValue) 136 | dropOne n = composeN n $ S.drop 1 137 | dropAll n = composeN n $ S.drop maxValue 138 | dropWhileFalse n = composeN n $ S.dropWhileL (> maxValue) 139 | dropWhileTrue n = composeN n $ S.dropWhileL (<= maxValue) 140 | 141 | ------------------------------------------------------------------------------- 142 | -- Iteration 143 | ------------------------------------------------------------------------------- 144 | 145 | iterStreamLen, maxIters :: Int 146 | iterStreamLen = 10 147 | maxIters = 100000 148 | 149 | {-# INLINE iterateSource #-} 150 | iterateSource :: (Stream Int -> Stream Int) -> Int -> Int -> Stream Int 151 | iterateSource g i n = f i (sourceN iterStreamLen n) 152 | where 153 | f (0 :: Int) m = g m 154 | f x m = g (f (x P.- 1) m) 155 | 156 | {-# INLINE iterateScan #-} 157 | {-# INLINE iterateFilterEven #-} 158 | {-# INLINE iterateTakeAll #-} 159 | {-# INLINE iterateDropOne #-} 160 | {-# INLINE iterateDropWhileFalse #-} 161 | {-# INLINE iterateDropWhileTrue #-} 162 | iterateScan, iterateFilterEven, iterateTakeAll, iterateDropOne, 163 | iterateDropWhileFalse, iterateDropWhileTrue :: Int -> Stream Int 164 | 165 | iterateScan = undefined 166 | iterateFilterEven n = iterateSource (S.filter even) maxIters n 167 | iterateTakeAll n = iterateSource (S.take maxValue) maxIters n 168 | iterateDropOne n = iterateSource (S.drop 1) maxIters n 169 | iterateDropWhileFalse n = iterateSource (S.dropWhileL (> maxValue)) maxIters n 170 | iterateDropWhileTrue n = iterateSource (S.dropWhileL (<= maxValue)) maxIters n 171 | 172 | ------------------------------------------------------------------------------- 173 | -- Mixed Composition 174 | ------------------------------------------------------------------------------- 175 | 176 | {-# INLINE scanMap #-} 177 | {-# INLINE dropMap #-} 178 | {-# INLINE dropScan #-} 179 | {-# INLINE takeDrop #-} 180 | {-# INLINE takeScan #-} 181 | {-# INLINE takeMap #-} 182 | {-# INLINE filterDrop #-} 183 | {-# INLINE filterTake #-} 184 | {-# INLINE filterScan #-} 185 | {-# INLINE filterMap #-} 186 | scanMap, dropMap, dropScan, takeDrop, takeScan, takeMap, filterDrop, 187 | filterTake, filterScan, filterMap 188 | :: Int -> Stream Int -> () 189 | 190 | scanMap = undefined -- composeN n $ S.map (subtract 1) . S.scanl' (+) 0 191 | dropMap n = composeN n $ P.fmap (subtract 1) . S.drop 1 192 | dropScan = undefined -- composeN n $ S.scanl' (+) 0 . S.drop 1 193 | takeDrop n = composeN n $ S.drop 1 . S.take maxValue 194 | takeScan = undefined -- composeN n $ S.scanl' (+) 0 . S.take maxValue 195 | takeMap n = composeN n $ P.fmap (subtract 1) . S.take maxValue 196 | filterDrop n = composeN n $ S.drop 1 . S.filter (<= maxValue) 197 | filterTake n = composeN n $ S.take maxValue . S.filter (<= maxValue) 198 | filterScan = undefined -- composeN n $ S.scanl' (+) 0 . S.filter (<= maxBound) 199 | filterMap n = composeN n $ P.fmap (subtract 1) . S.filter (<= maxValue) 200 | 201 | ------------------------------------------------------------------------------- 202 | -- Zipping and concat 203 | ------------------------------------------------------------------------------- 204 | 205 | {-# INLINE zip #-} 206 | zip :: Stream Int -> () 207 | zip src = transform $ (S.zipWith (,) src src) 208 | 209 | {-# INLINE concat #-} 210 | concat :: Stream Int -> Stream Int 211 | concat _src = undefined -- transform $ (S.concatMap (S.replicate 3) src) 212 | 213 | main :: P.IO () 214 | main = $(defaultMain "Sequence") 215 | -------------------------------------------------------------------------------- /Benchmarks/SimpleConduit.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : Benchmarks.SimpleConduit 3 | -- Copyright : (c) 2018 Harendra Kumar 4 | -- 5 | -- License : MIT 6 | -- Maintainer : harendra.kumar@gmail.com 7 | 8 | {-# LANGUAGE TemplateHaskell #-} 9 | 10 | module Benchmarks.SimpleConduit where 11 | 12 | import Benchmarks.DefaultMain (defaultMain) 13 | import Benchmarks.Common (value, maxValue) 14 | import Control.Monad (void) 15 | import Prelude 16 | (Monad, Int, (+), id, ($), (.), return, fmap, even, (>), (<=), 17 | subtract, undefined) 18 | 19 | import qualified Conduit.Simple as S 20 | 21 | ------------------------------------------------------------------------------- 22 | -- Benchmark ops 23 | ------------------------------------------------------------------------------- 24 | 25 | toNull, toList, foldl, last, scan, map, filterEven, mapM, filterAllOut, 26 | filterAllIn, takeOne, takeAll, takeWhileTrue, dropAll, dropWhileTrue, zip, 27 | concat, composeMapM, composeAllInFilters, composeAllOutFilters, 28 | composeMapAllInFilter 29 | :: Monad m 30 | => Int -> m () 31 | 32 | ------------------------------------------------------------------------------- 33 | -- Stream generation and elimination 34 | ------------------------------------------------------------------------------- 35 | 36 | type Stream m a = S.StreamT m a 37 | 38 | source :: Int -> Stream m Int 39 | source n = S.each [n..n+value] 40 | 41 | runStream :: Monad m => Stream m a -> m () 42 | runStream = S.runStreamT 43 | 44 | ------------------------------------------------------------------------------- 45 | -- Elimination 46 | ------------------------------------------------------------------------------- 47 | 48 | eliminate :: Monad m => (Stream m Int -> m a) -> Int -> m () 49 | eliminate f = void . f . source 50 | 51 | toNull = eliminate $ runStream 52 | toList = eliminate $ S.toList 53 | foldl = eliminate $ S.foldl (+) 0 id 54 | last = eliminate $ S.last 55 | 56 | ------------------------------------------------------------------------------- 57 | -- Transformation 58 | ------------------------------------------------------------------------------- 59 | 60 | transform :: Monad m => (Stream m Int -> Stream m a) -> Int -> m () 61 | transform f = runStream . f . source 62 | 63 | scan = transform $ S.scan (+) 0 id 64 | map = transform $ fmap (+1) 65 | mapM = transform $ S.mapM return 66 | filterEven = transform $ S.filter even 67 | filterAllOut = transform $ S.filter (> maxValue) 68 | filterAllIn = transform $ S.filter (<= maxValue) 69 | takeOne = transform $ S.take 1 70 | takeAll = transform $ S.take maxValue 71 | takeWhileTrue = transform $ S.takeWhile (<= maxValue) 72 | dropAll = transform $ S.drop maxValue 73 | dropWhileTrue = transform $ S.dropWhile (<= maxValue) 74 | 75 | ------------------------------------------------------------------------------- 76 | -- Zipping and concat 77 | ------------------------------------------------------------------------------- 78 | 79 | zip n = runStream $ (S.zipWith (,) (source n) (source n)) 80 | concat _n = undefined 81 | 82 | ------------------------------------------------------------------------------- 83 | -- Composition 84 | ------------------------------------------------------------------------------- 85 | 86 | compose :: Monad m => (Stream m Int -> Stream m Int) -> Int -> m () 87 | compose f = transform $ (f . f . f . f) 88 | 89 | composeMapM = compose (S.mapM return) 90 | composeAllInFilters = compose (S.filter (<= maxValue)) 91 | composeAllOutFilters = compose (S.filter (> maxValue)) 92 | composeMapAllInFilter = compose (S.filter (<= maxValue) . fmap (subtract 1)) 93 | 94 | composeScaling :: Monad m => Int -> Int -> m () 95 | composeScaling m n = 96 | case m of 97 | 1 -> transform f n 98 | 2 -> transform (f . f) n 99 | 3 -> transform (f . f . f) n 100 | 4 -> transform (f . f . f . f) n 101 | _ -> undefined 102 | where f = S.filter (<= maxValue) 103 | 104 | main :: P.IO () 105 | main = $(defaultMain "SimpleConduit") 106 | -------------------------------------------------------------------------------- /Benchmarks/Streaming.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : Benchmarks.Streaming 3 | -- Copyright : (c) 2018 Harendra Kumar 4 | -- 5 | -- License : MIT 6 | -- Maintainer : harendra.kumar@gmail.com 7 | 8 | {-# LANGUAGE TemplateHaskell #-} 9 | {-# OPTIONS_GHC -fno-warn-orphans #-} 10 | 11 | module Benchmarks.Streaming where 12 | 13 | import Benchmarks.DefaultMain (defaultMain) 14 | import Benchmarks.Common (value, maxValue, appendValue) 15 | import Control.DeepSeq (NFData) 16 | import Prelude 17 | (Monad, Int, (+), id, ($), (.), return, even, (>), (<=), 18 | subtract, undefined, Maybe, Either(..), foldMap, maxBound) 19 | import qualified Prelude as P 20 | import Data.Semigroup ((<>)) 21 | --import Prelude (replicate) 22 | 23 | import qualified Streaming.Prelude as S 24 | 25 | ------------------------------------------------------------------------------- 26 | -- Stream generation and elimination 27 | ------------------------------------------------------------------------------- 28 | 29 | -- Orphan instance to use nfIO on streaming 30 | instance (NFData a, NFData b) => NFData (S.Of a b) 31 | 32 | type Stream m a = S.Stream (S.Of a) m () 33 | 34 | {-# INLINE source #-} 35 | source :: Monad m => Int -> Stream m Int 36 | -- source n = S.each [n..n+value] 37 | source n = S.unfoldr step n 38 | where 39 | step cnt = 40 | if cnt > n + value 41 | then return $ Left () 42 | else return (Right (cnt, cnt + 1)) 43 | 44 | ------------------------------------------------------------------------------- 45 | -- Append 46 | ------------------------------------------------------------------------------- 47 | 48 | {-# INLINE appendSourceR #-} 49 | appendSourceR :: Int -> P.IO () 50 | appendSourceR n = 51 | toNull $ foldMap S.yield [n..n+appendValue] 52 | 53 | {-# INLINE appendSourceL #-} 54 | appendSourceL :: Int -> P.IO () 55 | appendSourceL n = 56 | toNull $ P.foldl (<>) P.mempty (P.map S.yield [n..n+appendValue]) 57 | 58 | ------------------------------------------------------------------------------- 59 | -- Elimination 60 | ------------------------------------------------------------------------------- 61 | 62 | {-# INLINE runStream #-} 63 | runStream :: Monad m => Stream m a -> m () 64 | runStream = S.mapM_ (\_ -> return ()) 65 | 66 | {-# INLINE toNull #-} 67 | {-# INLINE toList #-} 68 | {-# INLINE foldl #-} 69 | {-# INLINE last #-} 70 | toNull :: Monad m => Stream m Int -> m () 71 | toList :: Monad m => Stream m Int -> m (S.Of [Int] ()) 72 | foldl :: Monad m => Stream m Int -> m (S.Of Int ()) 73 | last :: Monad m => Stream m Int -> m (S.Of (Maybe Int) ()) 74 | 75 | toNull = runStream 76 | toList = S.toList 77 | foldl = S.fold (+) 0 id 78 | last = S.last 79 | 80 | ------------------------------------------------------------------------------- 81 | -- Transformation 82 | ------------------------------------------------------------------------------- 83 | 84 | {-# INLINE transform #-} 85 | transform :: Monad m => Stream m a -> m () 86 | transform = runStream 87 | 88 | {-# INLINE composeN #-} 89 | composeN 90 | :: Monad m 91 | => Int -> (Stream m Int -> Stream m Int) -> Stream m Int -> m () 92 | composeN n f = 93 | case n of 94 | 1 -> transform . f 95 | 2 -> transform . f . f 96 | 3 -> transform . f . f . f 97 | 4 -> transform . f . f . f . f 98 | _ -> undefined 99 | 100 | {-# INLINE scan #-} 101 | {-# INLINE map #-} 102 | {-# INLINE mapM #-} 103 | {-# INLINE filterEven #-} 104 | {-# INLINE filterAllOut #-} 105 | {-# INLINE filterAllIn #-} 106 | {-# INLINE takeOne #-} 107 | {-# INLINE takeAll #-} 108 | {-# INLINE takeWhileTrue #-} 109 | {-# INLINE dropOne #-} 110 | {-# INLINE dropAll #-} 111 | {-# INLINE dropWhileTrue #-} 112 | {-# INLINE dropWhileFalse #-} 113 | scan, map, mapM, 114 | filterEven, filterAllOut, filterAllIn, 115 | takeOne, takeAll, takeWhileTrue, 116 | dropOne, dropAll, dropWhileTrue, dropWhileFalse 117 | :: Monad m => Int -> Stream m Int -> m () 118 | 119 | scan n = composeN n $ S.scan (+) 0 id 120 | map n = composeN n $ S.map (+1) 121 | mapM n = composeN n $ S.mapM return 122 | filterEven n = composeN n $ S.filter even 123 | filterAllOut n = composeN n $ S.filter (> maxValue) 124 | filterAllIn n = composeN n $ S.filter (<= maxValue) 125 | takeOne n = composeN n $ S.take 1 126 | takeAll n = composeN n $ S.take maxValue 127 | takeWhileTrue n = composeN n $ S.takeWhile (<= maxValue) 128 | dropOne n = composeN n $ S.drop 1 129 | dropAll n = composeN n $ S.drop maxValue 130 | dropWhileFalse n = composeN n $ S.dropWhile (> maxValue) 131 | dropWhileTrue n = composeN n $ S.dropWhile (<= maxValue) 132 | 133 | ------------------------------------------------------------------------------- 134 | -- Mixed Composition 135 | ------------------------------------------------------------------------------- 136 | 137 | {-# INLINE scanMap #-} 138 | {-# INLINE dropMap #-} 139 | {-# INLINE dropScan #-} 140 | {-# INLINE takeDrop #-} 141 | {-# INLINE takeScan #-} 142 | {-# INLINE takeMap #-} 143 | {-# INLINE filterDrop #-} 144 | {-# INLINE filterTake #-} 145 | {-# INLINE filterScan #-} 146 | {-# INLINE filterMap #-} 147 | scanMap, dropMap, dropScan, takeDrop, takeScan, takeMap, filterDrop, 148 | filterTake, filterScan, filterMap 149 | :: Monad m => Int -> Stream m Int -> m () 150 | 151 | scanMap n = composeN n $ S.map (subtract 1) . S.scan (+) 0 id 152 | dropMap n = composeN n $ S.map (subtract 1) . S.drop 1 153 | dropScan n = composeN n $ S.scan (+) 0 id . S.drop 1 154 | takeDrop n = composeN n $ S.drop 1 . S.take maxValue 155 | takeScan n = composeN n $ S.scan (+) 0 id . S.take maxValue 156 | takeMap n = composeN n $ S.map (subtract 1) . S.take maxValue 157 | filterDrop n = composeN n $ S.drop 1 . S.filter (<= maxValue) 158 | filterTake n = composeN n $ S.take maxValue . S.filter (<= maxValue) 159 | filterScan n = composeN n $ S.scan (+) 0 id . S.filter (<= maxBound) 160 | filterMap n = composeN n $ S.map (subtract 1) . S.filter (<= maxValue) 161 | 162 | ------------------------------------------------------------------------------- 163 | -- Zipping and concat 164 | ------------------------------------------------------------------------------- 165 | 166 | {-# INLINE zip #-} 167 | zip :: Monad m => Stream m Int -> m () 168 | zip src = runStream $ (S.zip src src) 169 | 170 | {-# INLINE concatMapFoldable #-} 171 | concatMapFoldable :: Monad m => Stream m Int -> m () 172 | concatMapFoldable src = runStream $ (S.concat $ S.map (P.replicate 3) src) 173 | 174 | main :: P.IO () 175 | main = $(defaultMain "Streaming") 176 | -------------------------------------------------------------------------------- /Benchmarks/Streamly.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : Benchmarks.Streamly 3 | -- Copyright : (c) 2018 Harendra Kumar 4 | -- 5 | -- License : MIT 6 | -- Maintainer : harendra.kumar@gmail.com 7 | 8 | {-# LANGUAGE FlexibleContexts #-} 9 | {-# LANGUAGE ScopedTypeVariables #-} 10 | {-# LANGUAGE TemplateHaskell #-} 11 | {-# OPTIONS_GHC -Wno-identities #-} 12 | 13 | module Benchmarks.Streamly where 14 | 15 | import Benchmarks.DefaultMain (defaultMain) 16 | import Benchmarks.Common (value, maxValue, appendValue) 17 | import Prelude 18 | (Monad, Int, (+), ($), (.), return, even, (>), (<=), 19 | subtract, undefined, Maybe(..), maxBound, fmap) 20 | import qualified Prelude as P 21 | 22 | import qualified Streamly.Data.Fold as Fold 23 | import qualified Streamly.Data.Stream as S 24 | import qualified Streamly.Data.StreamK as K 25 | 26 | -- import Data.Word (Word8) 27 | 28 | -- To compare with ByteString we use an element of type Word8 29 | -- It does not seem to make any perceptible difference though 30 | type Element = Int 31 | -- type Element = Word8 32 | 33 | nElements :: Int 34 | nElements = maxValue 35 | 36 | maxElem :: Element 37 | maxElem = maxBound 38 | 39 | ------------------------------------------------------------------------------- 40 | -- Stream generation 41 | ------------------------------------------------------------------------------- 42 | 43 | type Stream m a = S.Stream m a 44 | type StreamK m a = K.StreamK m a 45 | 46 | {-# INLINE source #-} 47 | source :: Monad m => Int -> Stream m Element 48 | -- source n = S.fromFoldable [n..n+value] 49 | source n = S.unfoldrM step n 50 | where 51 | step cnt = 52 | if cnt > n + value 53 | then return Nothing 54 | else return (Just (P.fromIntegral cnt, cnt + 1)) 55 | {- 56 | source n = S.unfoldr step n 57 | where 58 | step cnt = 59 | if cnt > n + value 60 | then Nothing 61 | else (Just (cnt, cnt + 1)) 62 | -} 63 | 64 | {-# INLINE sourceN #-} 65 | sourceN :: Monad m => Int -> Int -> StreamK m Element 66 | sourceN count begin = K.fromStream $ S.unfoldrM step begin 67 | where 68 | step i = 69 | if i > begin + count 70 | then return Nothing 71 | else return (Just (P.fromIntegral i, i + 1)) 72 | 73 | {-# INLINE sourceIntFromThenTo #-} 74 | sourceIntFromThenTo :: Monad m => Int -> Stream m Element 75 | sourceIntFromThenTo n = 76 | fmap P.fromIntegral $ S.enumerateFromThenTo n (n + 1) (n + value) 77 | 78 | ------------------------------------------------------------------------------- 79 | -- Append 80 | ------------------------------------------------------------------------------- 81 | 82 | {-# INLINE appendSourceR #-} 83 | appendSourceR :: Int -> P.IO () 84 | appendSourceR n = 85 | K.drain $ P.foldr K.append K.nil (P.map K.fromPure [n..n+appendValue]) 86 | 87 | {-# INLINE appendSourceL #-} 88 | appendSourceL :: Int -> P.IO () 89 | appendSourceL n = 90 | K.drain $ P.foldl K.append K.nil (P.map K.fromPure [n..n+appendValue]) 91 | 92 | ------------------------------------------------------------------------------- 93 | -- Elimination 94 | ------------------------------------------------------------------------------- 95 | 96 | {-# INLINE runStream #-} 97 | runStream :: Monad m => Stream m a -> m () 98 | runStream = S.fold Fold.drain 99 | 100 | {-# INLINE toNull #-} 101 | {-# INLINE toList #-} 102 | {-# INLINE foldl #-} 103 | {-# INLINE last #-} 104 | toNull :: Monad m => Stream m Element -> m () 105 | toList :: Monad m => Stream m Element -> m [Element] 106 | foldl :: Monad m => Stream m Element -> m Element 107 | last :: Monad m => Stream m Element -> m (Maybe Element) 108 | 109 | toNull = runStream 110 | toList = S.toList 111 | foldl = S.fold (Fold.foldl' (+) 0) 112 | last = S.fold Fold.latest 113 | 114 | ------------------------------------------------------------------------------- 115 | -- Transformation 116 | ------------------------------------------------------------------------------- 117 | 118 | {-# INLINE transform #-} 119 | transform :: Monad m => Stream m a -> m () 120 | transform = runStream 121 | 122 | {-# INLINE composeN #-} 123 | composeN 124 | :: Monad m 125 | => Int -> (Stream m Element -> Stream m Element) -> Stream m Element -> m () 126 | composeN n f = 127 | case n of 128 | 1 -> transform . f 129 | 2 -> transform . f . f 130 | 3 -> transform . f . f . f 131 | 4 -> transform . f . f . f . f 132 | _ -> undefined 133 | 134 | {-# INLINE scan #-} 135 | {-# INLINE map #-} 136 | {-# INLINE mapM #-} 137 | {-# INLINE filterEven #-} 138 | {-# INLINE filterAllOut #-} 139 | {-# INLINE filterAllIn #-} 140 | {-# INLINE takeOne #-} 141 | {-# INLINE takeAll #-} 142 | {-# INLINE takeWhileTrue #-} 143 | {-# INLINE dropOne #-} 144 | {-# INLINE dropAll #-} 145 | {-# INLINE dropWhileTrue #-} 146 | {-# INLINE dropWhileFalse #-} 147 | scan, map, 148 | filterEven, filterAllOut, filterAllIn, 149 | takeOne, takeAll, takeWhileTrue, 150 | dropOne, dropAll, dropWhileTrue, dropWhileFalse 151 | :: Monad m => Int -> Stream m Element -> m () 152 | 153 | mapM :: Monad m => Int -> Stream m Element -> m () 154 | 155 | scan n = composeN n $ S.scan (Fold.foldl' (+) 0) 156 | map n = composeN n $ fmap (+1) 157 | mapM n = composeN n $ S.mapM return 158 | filterEven n = composeN n $ S.filter even 159 | filterAllOut n = composeN n $ S.filter (> maxElem) 160 | filterAllIn n = composeN n $ S.filter (<= maxElem) 161 | takeOne n = composeN n $ S.take 1 162 | takeAll n = composeN n $ S.take nElements 163 | takeWhileTrue n = composeN n $ S.takeWhile (<= maxElem) 164 | dropOne n = composeN n $ S.drop 1 165 | dropAll n = composeN n $ S.drop nElements 166 | dropWhileFalse n = composeN n $ S.dropWhile (> maxElem) 167 | dropWhileTrue n = composeN n $ S.dropWhile (<= maxElem) 168 | 169 | ------------------------------------------------------------------------------- 170 | -- Iteration 171 | ------------------------------------------------------------------------------- 172 | 173 | iterStreamLen, maxIters :: Int 174 | iterStreamLen = 10 175 | maxIters = 100000 176 | 177 | {-# INLINE iterateSource #-} 178 | iterateSource 179 | :: Monad m 180 | => (StreamK m Element -> StreamK m Element) 181 | -> Int 182 | -> Int 183 | -> StreamK m Element 184 | iterateSource g i n = f i (sourceN iterStreamLen n) 185 | 186 | where 187 | 188 | f (0 :: Int) m = g m 189 | f x m = g (f (x P.- 1) m) 190 | 191 | iterateMapM, iterateScan, iterateFilterEven, iterateTakeAll, iterateDropOne, 192 | iterateDropWhileFalse, iterateDropWhileTrue :: Monad m => Int -> Stream m Element 193 | 194 | -- Scan increases the size of the stream by 1, drop 1 to not blow up the size 195 | -- due to many iterations. 196 | iterateScan n = K.toStream $ iterateSource (K.fromStream . S.drop 1 . S.scan (Fold.foldl' (+) 0) . K.toStream) maxIters n 197 | -- iterateScan n = K.toStream $ iterateSource (K.drop 1 . K.scanl' (+) 0) maxIters n 198 | 199 | -- iterateMapM n = K.toStream $ iterateSource (K.fromStream . S.mapM return . K.toStream) maxIters n 200 | iterateMapM n = K.toStream $ iterateSource (K.mapM return) maxIters n 201 | 202 | -- The D version is very slow, investigate why. 203 | -- iterateDropWhileFalse n = K.toStream $ iterateSource (K.fromStream . S.dropWhile (> maxElem) . K.toStream) maxIters n 204 | iterateDropWhileFalse n = K.toStream $ iterateSource (K.dropWhile (> maxElem)) maxIters n 205 | 206 | -- iterateTakeAll n = K.toStream $ iterateSource (K.fromStream . S.take nElements . K.toStream) maxIters n 207 | iterateTakeAll n = K.toStream $ iterateSource (K.take nElements) maxIters n 208 | 209 | iterateFilterEven n = K.toStream $ iterateSource (K.fromStream . S.filter even . K.toStream) maxIters n 210 | -- iterateFilterEven n = K.toStream $ iterateSource (K.filter even) maxIters n 211 | 212 | iterateDropOne n = K.toStream $ iterateSource (K.fromStream . S.drop 1 . K.toStream) maxIters n 213 | -- iterateDropOne n = K.toStream $ iterateSource (K.drop 1) maxIters n 214 | 215 | -- iterateDropWhileTrue n = K.toStream $ iterateSource (K.fromStream . S.dropWhile (<= maxElem) . K.toStream) maxIters n 216 | iterateDropWhileTrue n = K.toStream $ iterateSource (K.dropWhile (<= maxElem)) maxIters n 217 | 218 | ------------------------------------------------------------------------------- 219 | -- Mixed Composition 220 | ------------------------------------------------------------------------------- 221 | 222 | {-# INLINE scanMap #-} 223 | {-# INLINE dropMap #-} 224 | {-# INLINE dropScan #-} 225 | {-# INLINE takeDrop #-} 226 | {-# INLINE takeScan #-} 227 | {-# INLINE takeMap #-} 228 | {-# INLINE filterDrop #-} 229 | {-# INLINE filterTake #-} 230 | {-# INLINE filterScan #-} 231 | {-# INLINE filterMap #-} 232 | scanMap, dropMap, dropScan, takeDrop, takeScan, takeMap, filterDrop, 233 | filterTake, filterScan, filterMap 234 | :: Monad m => Int -> Stream m Element -> m () 235 | 236 | scanMap n = composeN n $ fmap (subtract 1) . S.scan (Fold.foldl' (+) 0) 237 | dropMap n = composeN n $ fmap (subtract 1) . S.drop 1 238 | dropScan n = composeN n $ S.scan (Fold.foldl' (+) 0) . S.drop 1 239 | takeDrop n = composeN n $ S.drop 1 . S.take nElements 240 | takeScan n = composeN n $ S.scan (Fold.foldl' (+) 0) . S.take nElements 241 | takeMap n = composeN n $ fmap (subtract 1) . S.take nElements 242 | filterDrop n = composeN n $ S.drop 1 . S.filter (<= maxElem) 243 | filterTake n = composeN n $ S.take maxValue . S.filter (<= maxElem) 244 | filterScan n = composeN n $ S.scan (Fold.foldl' (+) 0) . S.filter (<= maxElem) 245 | filterMap n = composeN n $ fmap (subtract 1) . S.filter (<= maxElem) 246 | 247 | ------------------------------------------------------------------------------- 248 | -- Zipping and concat 249 | ------------------------------------------------------------------------------- 250 | 251 | {-# INLINE zip #-} 252 | zip :: Monad m => Stream m Element -> m () 253 | zip src = transform $ (S.zipWith (,) src src) 254 | 255 | {-# INLINE concatMap #-} 256 | concatMap :: Monad m => Stream m Element -> m () 257 | concatMap src = transform $ (S.concatMap (S.replicate 3) src) 258 | 259 | -- XXX composed zip and concat 260 | 261 | main :: P.IO () 262 | main = $(defaultMain "Streamly") 263 | -------------------------------------------------------------------------------- /Benchmarks/StreamlyArray.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : Benchmarks.StreamlyArray 3 | -- Copyright : (c) 2018 Harendra Kumar 4 | -- 5 | -- License : MIT 6 | -- Maintainer : harendra.kumar@gmail.com 7 | 8 | {-# LANGUAGE ScopedTypeVariables #-} 9 | {-# LANGUAGE FlexibleContexts #-} 10 | {-# LANGUAGE TemplateHaskell #-} 11 | {-# OPTIONS_GHC -Wno-orphans #-} 12 | 13 | module Benchmarks.StreamlyArray where 14 | 15 | import Control.DeepSeq (NFData(..)) 16 | import Control.Monad.IO.Class (MonadIO(..)) 17 | import Prelude (Int, (+), ($), (.), even, (>), (<=), subtract, undefined, 18 | maxBound, Maybe(..), fmap, div) 19 | import qualified Prelude as P 20 | 21 | import Benchmarks.DefaultMain (defaultMain) 22 | import Benchmarks.Common (value, maxValue) -- , appendValue) 23 | 24 | import qualified Streamly.Data.Stream as S 25 | import qualified Streamly.Data.Array as A 26 | import qualified Streamly.Data.Fold as Fold 27 | 28 | instance NFData (A.Array a) where 29 | {-# INLINE rnf #-} 30 | rnf _ = () 31 | 32 | ------------------------------------------------------------------------------- 33 | -- Stream generation and elimination 34 | ------------------------------------------------------------------------------- 35 | 36 | type Stream = A.Array 37 | 38 | {-# INLINE source #-} 39 | source :: MonadIO m => Int -> m (Stream Int) 40 | source n = S.fold (A.writeN value) (S.unfoldr step n) 41 | where 42 | step cnt = 43 | if cnt > n + value 44 | then Nothing 45 | else (Just (cnt, cnt + 1)) 46 | 47 | {-# INLINE sourceN #-} 48 | sourceN :: MonadIO m => Int -> Int -> m (Stream Int) 49 | sourceN count begin = S.fold (A.writeN value) (S.unfoldr step begin) 50 | where 51 | step i = 52 | if i > begin + count 53 | then Nothing 54 | else (Just (i, i + 1)) 55 | 56 | ------------------------------------------------------------------------------- 57 | -- Append 58 | ------------------------------------------------------------------------------- 59 | 60 | {- 61 | {-# INLINE appendSourceR #-} 62 | appendSourceR :: Int -> Stream Int 63 | appendSourceR n = 64 | P.foldr (S.++) S.empty (P.map S.singleton [n..n+appendValue]) 65 | 66 | {-# INLINE appendSourceL #-} 67 | appendSourceL :: Int -> Stream Int 68 | appendSourceL n = P.foldl (S.++) S.empty (P.map S.singleton [n..n+appendValue]) 69 | -} 70 | 71 | ------------------------------------------------------------------------------- 72 | -- Elimination 73 | ------------------------------------------------------------------------------- 74 | 75 | {-# INLINE toNull #-} 76 | toNull :: P.Monad m => Stream Int -> m (Stream Int) 77 | toNull = P.return 78 | 79 | {-# INLINE toList #-} 80 | toList :: P.Monad m => Stream Int -> m ([Int]) 81 | toList = P.return . A.toList 82 | 83 | {-# INLINE foldl #-} 84 | foldl :: MonadIO m => Stream Int -> m Int 85 | foldl = S.fold Fold.sum . S.unfold A.reader 86 | 87 | {-# INLINE last #-} 88 | last :: P.Monad m => Stream Int -> m (Maybe Int) 89 | last arr = P.return (A.getIndex (A.length arr P.- 1) arr) 90 | 91 | ------------------------------------------------------------------------------- 92 | -- Transformation 93 | ------------------------------------------------------------------------------- 94 | 95 | {- 96 | {-# INLINE transform #-} 97 | transform :: Stream a -> () 98 | transform = eval 99 | -} 100 | 101 | {-# INLINE composeN #-} 102 | composeN 103 | :: MonadIO m 104 | => Int 105 | -> (S.Stream m Int -> S.Stream m Int) 106 | -> Stream Int 107 | -> m (Stream Int) 108 | composeN n f x = 109 | case n of 110 | 1 -> S.fold A.write $ f $ S.unfold A.reader x 111 | 2 -> S.fold A.write $ f . f $ S.unfold A.reader x 112 | 3 -> S.fold A.write $ f . f . f $ S.unfold A.reader x 113 | 4 -> S.fold A.write $ f . f . f . f $ S.unfold A.reader x 114 | _ -> undefined 115 | 116 | {-# INLINE scan #-} 117 | {-# INLINE map #-} 118 | {-# INLINE mapM #-} 119 | {-# INLINE filterEven #-} 120 | {-# INLINE filterAllOut #-} 121 | {-# INLINE filterAllIn #-} 122 | {-# INLINE takeOne #-} 123 | {-# INLINE takeAll #-} 124 | {-# INLINE takeWhileTrue #-} 125 | {-# INLINE dropOne #-} 126 | {-# INLINE dropAll #-} 127 | {-# INLINE dropWhileTrue #-} 128 | {-# INLINE dropWhileFalse #-} 129 | scan, map, mapM, 130 | filterEven, filterAllOut, filterAllIn, 131 | takeOne, takeAll, takeWhileTrue, 132 | dropOne, dropAll, dropWhileTrue, dropWhileFalse 133 | :: MonadIO m => Int -> Stream Int -> m (Stream Int) 134 | 135 | scan n = composeN n $ S.scan (Fold.foldl' (+) 0) 136 | map n = composeN n $ fmap (+1) 137 | mapM n = composeN n $ S.mapM (\x -> P.return $ x + 1) 138 | filterEven n = composeN n $ S.filter even 139 | filterAllOut n = composeN n $ S.filter (> maxValue) 140 | filterAllIn n = composeN n $ S.filter (<= maxValue) 141 | takeOne n = composeN n $ S.take 1 142 | takeAll n = composeN n $ S.take maxValue 143 | takeWhileTrue n = composeN n $ S.takeWhile (<= maxValue) 144 | dropOne n = composeN n $ S.drop 1 145 | dropAll n = composeN n $ S.drop maxValue 146 | dropWhileFalse n = composeN n $ S.dropWhile (> maxValue) 147 | dropWhileTrue n = composeN n $ S.dropWhile (<= maxValue) 148 | 149 | ------------------------------------------------------------------------------- 150 | -- Iteration 151 | ------------------------------------------------------------------------------- 152 | 153 | iterStreamLen, maxIters :: Int 154 | iterStreamLen = 10 155 | maxIters = 100000 156 | 157 | {-# INLINE iterateSource #-} 158 | iterateSource :: MonadIO m 159 | => (S.Stream m Int -> S.Stream m Int) -> Int -> Int -> m (Stream Int) 160 | iterateSource g i n = 161 | sourceN iterStreamLen n P.>>= \a -> S.fold A.write (f i $ S.unfold A.reader a) 162 | where 163 | f (0 :: Int) m = g m 164 | f x m = g (f (x P.- 1) m) 165 | 166 | {-# INLINE iterateMapM #-} 167 | {-# INLINE iterateScan #-} 168 | {-# INLINE iterateFilterEven #-} 169 | {-# INLINE iterateTakeAll #-} 170 | {-# INLINE iterateDropOne #-} 171 | {-# INLINE iterateDropWhileFalse #-} 172 | {-# INLINE iterateDropWhileTrue #-} 173 | iterateMapM, iterateScan, iterateFilterEven, iterateTakeAll, iterateDropOne, 174 | iterateDropWhileFalse, iterateDropWhileTrue :: 175 | MonadIO m => Int -> m (Stream Int) 176 | 177 | -- Scan increases the size of the stream by 1, drop 1 to not blow up the size 178 | -- due to many iterations. 179 | iterateScan n = iterateSource (S.drop 1 . S.scan (Fold.foldl' (+) 0)) (maxIters `div` 100) n 180 | iterateMapM n = iterateSource (S.mapM P.return) maxIters n 181 | iterateFilterEven n = iterateSource (S.filter even) maxIters n 182 | iterateTakeAll n = iterateSource (S.take maxValue) maxIters n 183 | iterateDropOne n = iterateSource (S.drop 1) maxIters n 184 | iterateDropWhileFalse n = iterateSource (S.dropWhile (> maxValue)) (maxIters `div` 100) n 185 | iterateDropWhileTrue n = iterateSource (S.dropWhile (<= maxValue)) maxIters n 186 | 187 | ------------------------------------------------------------------------------- 188 | -- Mixed Composition 189 | ------------------------------------------------------------------------------- 190 | 191 | {-# INLINE scanMap #-} 192 | {-# INLINE dropMap #-} 193 | {-# INLINE dropScan #-} 194 | {-# INLINE takeDrop #-} 195 | {-# INLINE takeScan #-} 196 | {-# INLINE takeMap #-} 197 | {-# INLINE filterDrop #-} 198 | {-# INLINE filterTake #-} 199 | {-# INLINE filterScan #-} 200 | {-# INLINE filterMap #-} 201 | scanMap, dropMap, dropScan, takeDrop, takeScan, takeMap, filterDrop, 202 | filterTake, filterScan, filterMap 203 | :: MonadIO m => Int -> Stream Int -> m (Stream Int) 204 | 205 | scanMap n = composeN n $ fmap (subtract 1) . S.scan (Fold.foldl' (+) 0) 206 | dropMap n = composeN n $ fmap (subtract 1) . S.drop 1 207 | dropScan n = composeN n $ S.scan (Fold.foldl' (+) 0) . S.drop 1 208 | takeDrop n = composeN n $ S.drop 1 . S.take maxValue 209 | takeScan n = composeN n $ S.scan (Fold.foldl' (+) 0) . S.take maxValue 210 | takeMap n = composeN n $ fmap (subtract 1) . S.take maxValue 211 | filterDrop n = composeN n $ S.drop 1 . S.filter (<= maxValue) 212 | filterTake n = composeN n $ S.take maxValue . S.filter (<= maxValue) 213 | filterScan n = composeN n $ S.scan (Fold.foldl' (+) 0) . S.filter (<= maxBound) 214 | filterMap n = composeN n $ fmap (subtract 1) . S.filter (<= maxValue) 215 | 216 | ------------------------------------------------------------------------------- 217 | -- Zipping and concat 218 | ------------------------------------------------------------------------------- 219 | 220 | {- 221 | {-# INLINE zip #-} 222 | zip :: Stream Int -> () 223 | zip src = P.foldr (\(x,y) xs -> P.seq x (P.seq y xs)) () 224 | $ S.zipWith (,) src src 225 | 226 | {-# INLINE concat #-} 227 | concat :: Stream Int -> () 228 | concat src = transform $ (S.concatMap (S.replicate 3) src) 229 | -} 230 | 231 | main :: P.IO () 232 | main = $(defaultMain "StreamlyArray") 233 | -------------------------------------------------------------------------------- /Benchmarks/StreamlyPure.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : Benchmarks.StreamlyPure 3 | -- Copyright : (c) 2018 Harendra Kumar 4 | -- 5 | -- License : MIT 6 | -- Maintainer : harendra.kumar@gmail.com 7 | 8 | {-# LANGUAGE CPP #-} 9 | {-# LANGUAGE FlexibleContexts #-} 10 | {-# LANGUAGE ScopedTypeVariables #-} 11 | {-# LANGUAGE TemplateHaskell #-} 12 | 13 | module Benchmarks.StreamlyPure where 14 | 15 | import Benchmarks.DefaultMain (defaultMain) 16 | import Benchmarks.Common (value, maxValue, appendValue) 17 | import Data.Functor.Identity (Identity, runIdentity) 18 | import Prelude 19 | (Int, (+), ($), (.), even, (>), (<=), 20 | subtract, undefined, Maybe(..), maxBound, fmap) 21 | import qualified Prelude as P 22 | 23 | import qualified Streamly.Data.Fold as Fold 24 | import qualified Streamly.Data.Stream as S 25 | import qualified Streamly.Data.StreamK as K 26 | 27 | ------------------------------------------------------------------------------- 28 | -- Stream generation 29 | ------------------------------------------------------------------------------- 30 | 31 | type Stream = S.Stream Identity 32 | type StreamK = K.StreamK Identity 33 | 34 | maxElem :: Int 35 | maxElem = maxBound 36 | 37 | {-# INLINE source #-} 38 | source :: Int -> Stream Int 39 | source n = S.unfoldr step n 40 | where 41 | step cnt = 42 | if cnt > n + value 43 | then Nothing 44 | else (Just (cnt, cnt + 1)) 45 | 46 | {-# INLINE sourceN #-} 47 | sourceN :: Int -> Int -> StreamK Int 48 | sourceN count begin = K.fromStream $ S.unfoldr step begin 49 | where 50 | step i = 51 | if i > begin + count 52 | then Nothing 53 | else (Just (i, i + 1)) 54 | 55 | ------------------------------------------------------------------------------- 56 | -- Append 57 | ------------------------------------------------------------------------------- 58 | 59 | {-# INLINE appendSourceR #-} 60 | appendSourceR :: Int -> Identity () 61 | appendSourceR n = 62 | K.drain $ P.foldr K.append K.nil (P.map K.fromPure [n..n+appendValue]) 63 | 64 | {-# INLINE appendSourceL #-} 65 | appendSourceL :: Int -> Identity () 66 | appendSourceL n = 67 | K.drain $ P.foldl K.append K.nil (P.map K.fromPure [n..n+appendValue]) 68 | 69 | ------------------------------------------------------------------------------- 70 | -- Elimination 71 | ------------------------------------------------------------------------------- 72 | 73 | -- Using NFData for evaluation may be fraught with problems because of a 74 | -- non-optimal implementation of NFData instance. So we just evaluate each 75 | -- element of the stream using a fold. 76 | {-# INLINE eval #-} 77 | eval :: Stream a -> () 78 | eval = runIdentity . S.foldrM P.seq (P.return ()) 79 | 80 | -- eval foldable 81 | {-# INLINE evalF #-} 82 | evalF :: P.Foldable t => t a -> () 83 | evalF = P.foldr P.seq () 84 | 85 | {-# INLINE toNull #-} 86 | toNull :: Stream Int -> () 87 | toNull = eval 88 | 89 | {-# INLINE toList #-} 90 | toList :: Stream Int -> () 91 | toList = evalF . runIdentity . S.toList 92 | 93 | {-# INLINE foldl #-} 94 | foldl :: Stream Int -> Int 95 | foldl = runIdentity . S.fold (Fold.foldl' (+) 0) 96 | 97 | {-# INLINE last #-} 98 | last :: Stream Int -> Maybe Int 99 | last = runIdentity . S.fold Fold.latest 100 | 101 | ------------------------------------------------------------------------------- 102 | -- Transformation 103 | ------------------------------------------------------------------------------- 104 | 105 | {-# INLINE transform #-} 106 | transform :: Stream a -> () 107 | transform = eval 108 | 109 | {-# INLINE composeN #-} 110 | composeN :: Int -> (Stream Int -> Stream Int) -> Stream Int -> () 111 | 112 | composeN n f = 113 | case n of 114 | 1 -> transform . f 115 | 2 -> transform . f . f 116 | 3 -> transform . f . f . f 117 | 4 -> transform . f . f . f . f 118 | _ -> undefined 119 | 120 | {-# INLINE scan #-} 121 | {-# INLINE map #-} 122 | {-# INLINE mapM #-} 123 | {-# INLINE filterEven #-} 124 | {-# INLINE filterAllOut #-} 125 | {-# INLINE filterAllIn #-} 126 | {-# INLINE takeOne #-} 127 | {-# INLINE takeAll #-} 128 | {-# INLINE takeWhileTrue #-} 129 | {-# INLINE dropOne #-} 130 | {-# INLINE dropAll #-} 131 | {-# INLINE dropWhileTrue #-} 132 | {-# INLINE dropWhileFalse #-} 133 | scan, map, mapM, 134 | filterEven, filterAllOut, filterAllIn, 135 | takeOne, takeAll, takeWhileTrue, 136 | dropOne, dropAll, dropWhileTrue, dropWhileFalse 137 | :: Int -> Stream Int -> () 138 | 139 | scan n = composeN n $ S.scan (Fold.foldl' (+) 0) 140 | map n = composeN n $ fmap (+1) 141 | mapM = map 142 | filterEven n = composeN n $ S.filter even 143 | filterAllOut n = composeN n $ S.filter (> maxValue) 144 | filterAllIn n = composeN n $ S.filter (<= maxValue) 145 | takeOne n = composeN n $ S.take 1 146 | takeAll n = composeN n $ S.take maxValue 147 | takeWhileTrue n = composeN n $ S.takeWhile (<= maxValue) 148 | dropOne n = composeN n $ S.drop 1 149 | dropAll n = composeN n $ S.drop maxValue 150 | dropWhileFalse n = composeN n $ S.dropWhile (> maxValue) 151 | dropWhileTrue n = composeN n $ S.dropWhile (<= maxValue) 152 | 153 | ------------------------------------------------------------------------------- 154 | -- Iteration 155 | ------------------------------------------------------------------------------- 156 | 157 | iterStreamLen, maxIters :: Int 158 | iterStreamLen = 10 159 | maxIters = 100000 160 | 161 | {-# INLINE iterateSource #-} 162 | iterateSource 163 | :: (StreamK Int -> StreamK Int) 164 | -> Int 165 | -> Int 166 | -> StreamK Int 167 | iterateSource g i n = f i (sourceN iterStreamLen n) 168 | 169 | where 170 | 171 | f (0 :: Int) m = g m 172 | f x m = g (f (x P.- 1) m) 173 | 174 | iterateMapM, iterateScan, iterateFilterEven, iterateTakeAll, iterateDropOne, 175 | iterateDropWhileFalse, iterateDropWhileTrue :: Int -> Stream Int 176 | 177 | -- Scan increases the size of the stream by 1, drop 1 to not blow up the size 178 | -- due to many iterations. 179 | iterateScan n = K.toStream $ iterateSource (K.fromStream . S.drop 1 . S.scan (Fold.foldl' (+) 0) . K.toStream) (maxIters `P.div` 100) n 180 | -- iterateScan n = K.toStream $ iterateSource (K.scanl' (+) 0) (maxIters `div` 100) n 181 | 182 | -- iterateMapM n = K.toStream $ iterateSource (K.fromStream . S.mapM return . K.toStream) maxIters n 183 | iterateMapM n = K.toStream $ iterateSource (K.mapM P.return) maxIters n 184 | 185 | -- The D version is very slow, investigate why. 186 | -- iterateDropWhileFalse n = K.toStream $ iterateSource (K.fromStream . S.dropWhile (> maxElem) . K.toStream) maxIters n 187 | iterateDropWhileFalse n = K.toStream $ iterateSource (K.dropWhile (> maxElem)) maxIters n 188 | 189 | -- iterateTakeAll n = K.toStream $ iterateSource (K.fromStream . S.take maxValue . K.toStream) maxIters n 190 | iterateTakeAll n = K.toStream $ iterateSource (K.take maxValue) maxIters n 191 | 192 | iterateFilterEven n = K.toStream $ iterateSource (K.fromStream . S.filter even . K.toStream) maxIters n 193 | -- iterateFilterEven n = K.toStream $ iterateSource (K.filter even) maxIters n 194 | 195 | iterateDropOne n = K.toStream $ iterateSource (K.fromStream . S.drop 1 . K.toStream) maxIters n 196 | -- iterateDropOne n = K.toStream $ iterateSource (K.drop 1) maxIters n 197 | 198 | -- iterateDropWhileTrue n = K.toStream $ iterateSource (K.fromStream . S.dropWhile (<= maxElem) . K.toStream) maxIters n 199 | iterateDropWhileTrue n = K.toStream $ iterateSource (K.dropWhile (<= maxElem)) maxIters n 200 | 201 | ------------------------------------------------------------------------------- 202 | -- Mixed Composition 203 | ------------------------------------------------------------------------------- 204 | 205 | {-# INLINE scanMap #-} 206 | {-# INLINE dropMap #-} 207 | {-# INLINE dropScan #-} 208 | {-# INLINE takeDrop #-} 209 | {-# INLINE takeScan #-} 210 | {-# INLINE takeMap #-} 211 | {-# INLINE filterDrop #-} 212 | {-# INLINE filterTake #-} 213 | {-# INLINE filterScan #-} 214 | {-# INLINE filterMap #-} 215 | scanMap, dropMap, dropScan, takeDrop, takeScan, takeMap, filterDrop, 216 | filterTake, filterScan, filterMap 217 | :: Int -> Stream Int -> () 218 | 219 | scanMap n = composeN n $ fmap (subtract 1) . S.scan (Fold.foldl' (+) 0) 220 | dropMap n = composeN n $ fmap (subtract 1) . S.drop 1 221 | dropScan n = composeN n $ S.scan (Fold.foldl' (+) 0) . S.drop 1 222 | takeDrop n = composeN n $ S.drop 1 . S.take maxValue 223 | takeScan n = composeN n $ S.scan (Fold.foldl' (+) 0) . S.take maxValue 224 | takeMap n = composeN n $ fmap (subtract 1) . S.take maxValue 225 | filterDrop n = composeN n $ S.drop 1 . S.filter (<= maxValue) 226 | filterTake n = composeN n $ S.take maxValue . S.filter (<= maxValue) 227 | filterScan n = composeN n $ S.scan (Fold.foldl' (+) 0) . S.filter (<= maxBound) 228 | filterMap n = composeN n $ fmap (subtract 1) . S.filter (<= maxValue) 229 | 230 | ------------------------------------------------------------------------------- 231 | -- Zipping and concat 232 | ------------------------------------------------------------------------------- 233 | 234 | {-# INLINE zip #-} 235 | zip :: Stream Int -> () 236 | zip src = runIdentity $ S.foldr (\(x,y) xs -> P.seq x (P.seq y xs)) () 237 | $ S.zipWith (,) src src 238 | 239 | {-# INLINE concatMap #-} 240 | concatMap :: Stream Int -> () 241 | concatMap src = transform $ (S.concatMap (S.replicate 3) src) 242 | 243 | main :: P.IO () 244 | main = $(defaultMain "StreamlyPure") 245 | -------------------------------------------------------------------------------- /Benchmarks/Text.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : Benchmarks.Text 3 | -- Copyright : (c) 2018 Harendra Kumar 4 | -- 5 | -- License : MIT 6 | -- Maintainer : harendra.kumar@gmail.com 7 | 8 | {-# LANGUAGE ScopedTypeVariables #-} 9 | {-# LANGUAGE TemplateHaskell #-} 10 | 11 | module Benchmarks.Text where 12 | 13 | import Benchmarks.DefaultMain (defaultMain) 14 | -- import Benchmarks.Common (value, maxValue, appendValue) 15 | import Prelude (Int, (+), ($), (.), even, (>), (<=), undefined, 16 | Maybe(..), Char) 17 | import qualified Prelude as P 18 | import Data.Char (chr, ord) 19 | 20 | import qualified Data.Text as S 21 | 22 | nElements, nAppends :: Int 23 | nElements = 1000000 24 | nAppends = 10000 25 | 26 | minElem, maxElem :: Char 27 | minElem = chr 0 28 | maxElem = P.maxBound 29 | 30 | ------------------------------------------------------------------------------- 31 | -- Stream generation and elimination 32 | ------------------------------------------------------------------------------- 33 | 34 | type Element = Char 35 | type Stream a = S.Text 36 | 37 | {-# INLINE sourceN #-} 38 | sourceN :: Int -> Int -> Stream Element 39 | sourceN count begin = S.unfoldr step begin 40 | where 41 | step i = 42 | if i > begin + count 43 | then Nothing 44 | else (Just (chr (i `P.mod` 10000), i + 1)) 45 | 46 | {-# INLINE source #-} 47 | source :: Int -> Stream Element 48 | source = sourceN nElements 49 | 50 | ------------------------------------------------------------------------------- 51 | -- Append 52 | ------------------------------------------------------------------------------- 53 | 54 | {-# INLINE appendSourceR #-} 55 | appendSourceR :: Int -> () 56 | appendSourceR n = 57 | toNull $ P.foldr (S.append) S.empty (P.map (S.singleton . chr) [n..n+nAppends]) 58 | 59 | {-# INLINE appendSourceL #-} 60 | appendSourceL :: Int -> () 61 | appendSourceL n = 62 | toNull $ P.foldl (S.append) S.empty (P.map (S.singleton . chr) [n..n+nAppends]) 63 | 64 | ------------------------------------------------------------------------------- 65 | -- Elimination 66 | ------------------------------------------------------------------------------- 67 | 68 | -- Using NFData for evaluation may be fraught with problems because of a 69 | -- non-optimal implementation of NFData instance. So we just evaluate each 70 | -- element of the stream using a fold. 71 | {-# INLINE eval #-} 72 | eval :: Stream a -> () 73 | eval = S.foldr P.seq () 74 | 75 | -- eval foldable 76 | {-# INLINE evalF #-} 77 | evalF :: P.Foldable t => t a -> () 78 | evalF = P.foldr P.seq () 79 | 80 | plus :: Char -> Char -> Char 81 | plus x y = chr $ (ord x + ord y) `P.mod` 10000 82 | 83 | {-# INLINE toNull #-} 84 | toNull :: Stream Element -> () 85 | toNull = eval 86 | 87 | {-# INLINE toList #-} 88 | toList :: Stream Element -> () 89 | toList = evalF . S.unpack 90 | 91 | {-# INLINE foldl #-} 92 | foldl :: Stream Element -> Element 93 | foldl = S.foldl' plus (chr 0) 94 | 95 | {-# INLINE last #-} 96 | last :: Stream Element -> Element 97 | last = S.last 98 | 99 | ------------------------------------------------------------------------------- 100 | -- Transformation 101 | ------------------------------------------------------------------------------- 102 | 103 | {-# INLINE transform #-} 104 | transform :: Stream a -> () 105 | transform = eval 106 | 107 | {-# INLINE composeN #-} 108 | composeN :: Int 109 | -> (Stream Element -> Stream Element) 110 | -> Stream Element 111 | -> () 112 | composeN n f = 113 | case n of 114 | 1 -> transform . f 115 | 2 -> transform . f . f 116 | 3 -> transform . f . f . f 117 | 4 -> transform . f . f . f . f 118 | _ -> undefined 119 | 120 | {-# INLINE scan #-} 121 | {-# INLINE map #-} 122 | {-# INLINE mapM #-} 123 | {-# INLINE filterEven #-} 124 | {-# INLINE filterAllOut #-} 125 | {-# INLINE filterAllIn #-} 126 | {-# INLINE takeOne #-} 127 | {-# INLINE takeAll #-} 128 | {-# INLINE takeWhileTrue #-} 129 | {-# INLINE dropOne #-} 130 | {-# INLINE dropAll #-} 131 | {-# INLINE dropWhileTrue #-} 132 | {-# INLINE dropWhileFalse #-} 133 | scan, map, mapM, 134 | filterEven, filterAllOut, filterAllIn, 135 | takeOne, takeAll, takeWhileTrue, 136 | dropOne, dropAll, dropWhileTrue, dropWhileFalse 137 | :: Int -> Stream Int -> () 138 | 139 | -- XXX there is no scanl' 140 | scan n = composeN n $ S.scanl plus (chr 0) 141 | map n = composeN n $ S.map (plus (chr 1)) 142 | mapM = map 143 | filterEven n = composeN n $ S.filter (even . ord) 144 | filterAllOut n = composeN n $ S.filter (> maxElem) 145 | filterAllIn n = composeN n $ S.filter (<= maxElem) 146 | takeOne n = composeN n $ S.take 1 147 | takeAll n = composeN n $ S.take nElements 148 | takeWhileTrue n = composeN n $ S.takeWhile (<= maxElem) 149 | dropOne n = composeN n $ S.drop 1 150 | dropAll n = composeN n $ S.drop nElements 151 | dropWhileFalse n = composeN n $ S.dropWhile (> maxElem) 152 | dropWhileTrue n = composeN n $ S.dropWhile (<= maxElem) 153 | 154 | ------------------------------------------------------------------------------- 155 | -- Iteration 156 | ------------------------------------------------------------------------------- 157 | 158 | iterStreamLen, maxIters :: Int 159 | iterStreamLen = 10 160 | maxIters = 100000 161 | 162 | {-# INLINE iterateSource #-} 163 | iterateSource :: (Stream Element -> Stream Element) 164 | -> Int 165 | -> Int 166 | -> Stream Element 167 | iterateSource g i n = f i (sourceN iterStreamLen n) 168 | where 169 | f (0 :: Int) m = g m 170 | f x m = g (f (x P.- 1) m) 171 | 172 | {-# INLINE iterateScan #-} 173 | {-# INLINE iterateFilterEven #-} 174 | {-# INLINE iterateTakeAll #-} 175 | {-# INLINE iterateDropOne #-} 176 | {-# INLINE iterateDropWhileFalse #-} 177 | {-# INLINE iterateDropWhileTrue #-} 178 | iterateScan, iterateFilterEven, iterateTakeAll, iterateDropOne, 179 | iterateDropWhileFalse, iterateDropWhileTrue :: Int -> Stream Element 180 | 181 | -- XXX using scanl instead of scanl' 182 | -- Scan increases the size of the stream by 1, drop 1 to not blow up the size 183 | -- due to many iterations. 184 | iterateScan n = iterateSource (S.drop 1 . S.scanl plus (chr 0)) maxIters n 185 | iterateFilterEven n = iterateSource (S.filter (even . ord)) maxIters n 186 | iterateTakeAll n = iterateSource (S.take nElements) maxIters n 187 | iterateDropOne n = iterateSource (S.drop 1) maxIters n 188 | iterateDropWhileFalse n = iterateSource (S.dropWhile (> maxElem)) maxIters n 189 | iterateDropWhileTrue n = iterateSource (S.dropWhile (<= maxElem)) maxIters n 190 | 191 | ------------------------------------------------------------------------------- 192 | -- Mixed Composition 193 | ------------------------------------------------------------------------------- 194 | 195 | {-# INLINE scanMap #-} 196 | {-# INLINE dropMap #-} 197 | {-# INLINE dropScan #-} 198 | {-# INLINE takeDrop #-} 199 | {-# INLINE takeScan #-} 200 | {-# INLINE takeMap #-} 201 | {-# INLINE filterDrop #-} 202 | {-# INLINE filterTake #-} 203 | {-# INLINE filterScan #-} 204 | {-# INLINE filterMap #-} 205 | scanMap, dropMap, dropScan, takeDrop, takeScan, takeMap, filterDrop, 206 | filterTake, filterScan, filterMap 207 | :: Int -> Stream Element -> () 208 | 209 | -- XXX using scanl instead of scanl' 210 | scanMap n = composeN n $ S.map (plus (chr 1)) . S.scanl plus (chr 0) 211 | dropMap n = composeN n $ S.map (plus (chr 1)) . S.drop 1 212 | dropScan n = composeN n $ S.scanl plus (chr 0) . S.drop 1 213 | takeDrop n = composeN n $ S.drop 1 . S.take nElements 214 | takeScan n = composeN n $ S.scanl plus (chr 0) . S.take nElements 215 | takeMap n = composeN n $ S.map (plus (chr 1)) . S.take nElements 216 | filterDrop n = composeN n $ S.drop 1 . S.filter (<= maxElem) 217 | filterTake n = composeN n $ S.take nElements . S.filter (<= maxElem) 218 | filterScan n = composeN n $ S.scanl plus (chr 0) . S.filter (<= maxElem) 219 | filterMap n = composeN n $ S.map (plus (chr 1)) . S.filter (<= maxElem) 220 | 221 | ------------------------------------------------------------------------------- 222 | -- Zipping and concat 223 | ------------------------------------------------------------------------------- 224 | 225 | {-# INLINE zip #-} 226 | zip :: Stream Element -> () 227 | zip src = eval $ S.zipWith plus src src 228 | 229 | {-# INLINE concatMap #-} 230 | concatMap :: Stream Element -> () 231 | concatMap src = transform $ (S.concatMap (S.pack . P.replicate 3) src) 232 | 233 | main :: P.IO () 234 | main = $(defaultMain "Text") 235 | -------------------------------------------------------------------------------- /Benchmarks/Vector.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : Benchmarks.Vector 3 | -- Copyright : (c) 2018 Harendra Kumar 4 | -- 5 | -- License : MIT 6 | -- Maintainer : harendra.kumar@gmail.com 7 | 8 | {-# LANGUAGE CPP #-} 9 | {-# LANGUAGE ScopedTypeVariables #-} 10 | {-# LANGUAGE TemplateHaskell #-} 11 | 12 | module Benchmarks.Vector where 13 | 14 | import Benchmarks.DefaultMain (defaultMain) 15 | #define VECTOR_BOXED 16 | #include "VectorCommon.hs" 17 | 18 | main :: P.IO () 19 | main = $(defaultMain "Vector") 20 | -------------------------------------------------------------------------------- /Benchmarks/VectorCommon.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : Benchmarks.Vector 3 | -- Copyright : (c) 2018 Harendra Kumar 4 | -- 5 | -- License : MIT 6 | -- Maintainer : harendra.kumar@gmail.com 7 | 8 | -- {-# LANGUAGE CPP #-} 9 | -- {-# LANGUAGE ScopedTypeVariables #-} 10 | 11 | import Benchmarks.Common (value, maxValue, appendValue) 12 | import Prelude (Int, (+), ($), (.), even, (>), (<=), subtract, undefined, 13 | maxBound, Maybe(..)) 14 | import qualified Prelude as P 15 | 16 | #ifdef VECTOR_BOXED 17 | import qualified Data.Vector as S 18 | #define CONSTRAINT(a) 19 | #elif defined (VECTOR_UNBOXED) 20 | #define CONSTRAINT(a) S.Unbox a => 21 | import qualified Data.Vector.Unboxed as S 22 | #else 23 | #define CONSTRAINT(a) Storable a => 24 | import Foreign.Storable (Storable) 25 | import qualified Data.Vector.Storable as S 26 | #endif 27 | 28 | ------------------------------------------------------------------------------- 29 | -- Stream generation and elimination 30 | ------------------------------------------------------------------------------- 31 | 32 | type Stream = S.Vector 33 | 34 | {-# INLINE source #-} 35 | source :: Int -> Stream Int 36 | -- source v = S.fromList [v..v+value] 37 | 38 | source n = let v = S.unfoldr step n in v S.++ S.singleton (S.unsafeLast v) 39 | where 40 | step cnt = 41 | if cnt > n + value 42 | then Nothing 43 | else (Just (cnt, cnt + 1)) 44 | 45 | {-# INLINE sourceN #-} 46 | sourceN :: Int -> Int -> Stream Int 47 | sourceN count begin = let v = S.unfoldr step begin in v S.++ S.singleton (S.unsafeLast v) 48 | where 49 | step i = 50 | if i > begin + count 51 | then Nothing 52 | else (Just (i, i + 1)) 53 | 54 | ------------------------------------------------------------------------------- 55 | -- Append 56 | ------------------------------------------------------------------------------- 57 | 58 | {-# INLINE appendSourceR #-} 59 | appendSourceR :: Int -> () 60 | appendSourceR n = 61 | toNull $ P.foldr (S.++) S.empty (P.map S.singleton [n..n+appendValue]) 62 | 63 | {-# INLINE appendSourceL #-} 64 | appendSourceL :: Int -> () 65 | appendSourceL n = 66 | toNull $ P.foldl (S.++) S.empty (P.map S.singleton [n..n+appendValue]) 67 | 68 | ------------------------------------------------------------------------------- 69 | -- Elimination 70 | ------------------------------------------------------------------------------- 71 | 72 | -- Using NFData for evaluation may be fraught with problems because of a 73 | -- non-optimal implementation of NFData instance. So we just evaluate each 74 | -- element of the stream using a fold. 75 | {-# INLINE eval #-} 76 | eval :: CONSTRAINT(a) Stream a -> () 77 | eval = S.foldr P.seq () 78 | 79 | -- eval foldable 80 | {-# INLINE evalF #-} 81 | evalF :: P.Foldable t => t a -> () 82 | evalF = P.foldr P.seq () 83 | 84 | {-# INLINE toNull #-} 85 | toNull :: Stream Int -> () 86 | toNull = eval 87 | 88 | {-# INLINE toList #-} 89 | toList :: Stream Int -> () 90 | toList = evalF . S.toList 91 | 92 | {-# INLINE foldl #-} 93 | foldl :: Stream Int -> Int 94 | foldl = S.foldl' (+) 0 95 | 96 | {-# INLINE last #-} 97 | last :: Stream Int -> Int 98 | last = S.last 99 | 100 | ------------------------------------------------------------------------------- 101 | -- Transformation 102 | ------------------------------------------------------------------------------- 103 | 104 | {-# INLINE transform #-} 105 | transform :: CONSTRAINT(a) Stream a -> () 106 | transform = eval 107 | 108 | {-# INLINE composeN #-} 109 | composeN :: Int -> (Stream Int -> Stream Int) -> Stream Int -> () 110 | composeN n f x = 111 | case n of 112 | 1 -> (transform . f) x 113 | 2 -> (transform . f . f) x 114 | 3 -> (transform . f . f . f) x 115 | 4 -> let v = (f . f . f . f) x in transform (v S.++ S.singleton (S.unsafeLast v)) 116 | _ -> undefined 117 | 118 | {-# INLINE scan #-} 119 | {-# INLINE map #-} 120 | {-# INLINE mapM #-} 121 | {-# INLINE filterEven #-} 122 | {-# INLINE filterAllOut #-} 123 | {-# INLINE filterAllIn #-} 124 | {-# INLINE takeOne #-} 125 | {-# INLINE takeAll #-} 126 | {-# INLINE takeWhileTrue #-} 127 | {-# INLINE dropOne #-} 128 | {-# INLINE dropAll #-} 129 | {-# INLINE dropWhileTrue #-} 130 | {-# INLINE dropWhileFalse #-} 131 | scan, map, mapM, 132 | filterEven, filterAllOut, filterAllIn, 133 | takeOne, takeAll, takeWhileTrue, 134 | dropOne, dropAll, dropWhileTrue, dropWhileFalse 135 | :: Int -> Stream Int -> () 136 | 137 | scan n = composeN n $ S.scanl' (+) 0 138 | map n = composeN n $ S.map (+1) 139 | mapM = map 140 | filterEven n = composeN n $ S.filter even 141 | filterAllOut n = composeN n $ S.filter (> maxValue) 142 | filterAllIn n = composeN n $ S.filter (<= maxValue) 143 | takeOne n = composeN n $ S.take 1 144 | takeAll n = composeN n $ S.take maxValue 145 | takeWhileTrue n = composeN n $ S.takeWhile (<= maxValue) 146 | dropOne n = composeN n $ S.drop 1 147 | dropAll n = composeN n $ S.drop maxValue 148 | dropWhileFalse n = composeN n $ S.dropWhile (> maxValue) 149 | dropWhileTrue n = composeN n $ S.dropWhile (<= maxValue) 150 | 151 | ------------------------------------------------------------------------------- 152 | -- Iteration 153 | ------------------------------------------------------------------------------- 154 | 155 | iterStreamLen, maxIters :: Int 156 | iterStreamLen = 10 157 | maxIters = 100000 158 | 159 | {-# INLINE iterateSource #-} 160 | iterateSource :: (Stream Int -> Stream Int) -> Int -> Int -> Stream Int 161 | iterateSource g i n = f i (sourceN iterStreamLen n) 162 | where 163 | f (0 :: Int) m = g m 164 | f x m = g (f (x P.- 1) m) 165 | 166 | {-# INLINE iterateScan #-} 167 | {-# INLINE iterateFilterEven #-} 168 | {-# INLINE iterateTakeAll #-} 169 | {-# INLINE iterateDropOne #-} 170 | {-# INLINE iterateDropWhileFalse #-} 171 | {-# INLINE iterateDropWhileTrue #-} 172 | iterateScan, iterateFilterEven, iterateTakeAll, iterateDropOne, 173 | iterateDropWhileFalse, iterateDropWhileTrue :: Int -> Stream Int 174 | 175 | -- Scan increases the size of the stream by 1, drop 1 to not blow up the size 176 | -- due to many iterations. 177 | iterateScan n = iterateSource (S.drop 1 . S.scanl' (+) 0) maxIters n 178 | iterateFilterEven n = iterateSource (S.filter even) maxIters n 179 | iterateTakeAll n = iterateSource (S.take maxValue) maxIters n 180 | iterateDropOne n = iterateSource (S.drop 1) maxIters n 181 | iterateDropWhileFalse n = iterateSource (S.dropWhile (> maxValue)) maxIters n 182 | iterateDropWhileTrue n = iterateSource (S.dropWhile (<= maxValue)) maxIters n 183 | 184 | ------------------------------------------------------------------------------- 185 | -- Mixed Composition 186 | ------------------------------------------------------------------------------- 187 | 188 | {-# INLINE scanMap #-} 189 | {-# INLINE dropMap #-} 190 | {-# INLINE dropScan #-} 191 | {-# INLINE takeDrop #-} 192 | {-# INLINE takeScan #-} 193 | {-# INLINE takeMap #-} 194 | {-# INLINE filterDrop #-} 195 | {-# INLINE filterTake #-} 196 | {-# INLINE filterScan #-} 197 | {-# INLINE filterMap #-} 198 | scanMap, dropMap, dropScan, takeDrop, takeScan, takeMap, filterDrop, 199 | filterTake, filterScan, filterMap 200 | :: Int -> Stream Int -> () 201 | 202 | scanMap n = composeN n $ S.map (subtract 1) . S.scanl' (+) 0 203 | dropMap n = composeN n $ S.map (subtract 1) . S.drop 1 204 | dropScan n = composeN n $ S.scanl' (+) 0 . S.drop 1 205 | takeDrop n = composeN n $ S.drop 1 . S.take maxValue 206 | takeScan n = composeN n $ S.scanl' (+) 0 . S.take maxValue 207 | takeMap n = composeN n $ S.map (subtract 1) . S.take maxValue 208 | filterDrop n = composeN n $ S.drop 1 . S.filter (<= maxValue) 209 | filterTake n = composeN n $ S.take maxValue . S.filter (<= maxValue) 210 | filterScan n = composeN n $ S.scanl' (+) 0 . S.filter (<= maxBound) 211 | filterMap n = composeN n $ S.map (subtract 1) . S.filter (<= maxValue) 212 | 213 | ------------------------------------------------------------------------------- 214 | -- Zipping and concat 215 | ------------------------------------------------------------------------------- 216 | 217 | #define STORABLE_VECTOR 218 | {-# INLINE zip #-} 219 | #ifndef STORABLE_VECTOR 220 | zip :: Stream Int -> () 221 | zip src = P.foldr (\(x,y) xs -> P.seq x (P.seq y xs)) () 222 | $ S.zipWith (,) src src 223 | #else 224 | zip :: Stream Int -> Stream Int 225 | zip src = S.zipWith (+) src src 226 | #endif 227 | 228 | {-# INLINE concatMap #-} 229 | concatMap :: Stream Int -> () 230 | concatMap src = transform $ (S.concatMap (S.replicate 3) src) 231 | -------------------------------------------------------------------------------- /Benchmarks/VectorStorable.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : Benchmarks.Vector 3 | -- Copyright : (c) 2018 Harendra Kumar 4 | -- 5 | -- License : MIT 6 | -- Maintainer : harendra.kumar@gmail.com 7 | 8 | {-# LANGUAGE CPP #-} 9 | {-# LANGUAGE ScopedTypeVariables #-} 10 | {-# LANGUAGE TemplateHaskell #-} 11 | 12 | module Benchmarks.VectorStorable where 13 | 14 | import Benchmarks.DefaultMain (defaultMain) 15 | #include "VectorCommon.hs" 16 | 17 | main :: P.IO () 18 | main = $(defaultMain "VectorStorable") 19 | -------------------------------------------------------------------------------- /Benchmarks/VectorStreams.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : Benchmarks.VectorStreams 3 | -- Copyright : (c) 2018 Harendra Kumar 4 | -- (c) 2018 Philipp Schuster 5 | -- 6 | -- License : MIT 7 | -- Maintainer : harendra.kumar@gmail.com 8 | 9 | {-# LANGUAGE ScopedTypeVariables #-} 10 | {-# LANGUAGE TemplateHaskell #-} 11 | 12 | module Benchmarks.VectorStreams where 13 | 14 | import Benchmarks.DefaultMain (defaultMain) 15 | import Benchmarks.Common (value, maxValue, appendValue) 16 | import Prelude 17 | (Monad, Int, (+), ($), (.), return, even, (>), (<=), 18 | subtract, undefined, Maybe(..)) 19 | import qualified Prelude as P 20 | 21 | import qualified Data.Vector.Fusion.Stream.Monadic as S 22 | 23 | ------------------------------------------------------------------------------- 24 | -- Stream generation and elimination 25 | ------------------------------------------------------------------------------- 26 | 27 | type Stream m a = S.Stream m a 28 | 29 | {-# INLINE source #-} 30 | source :: Monad m => Int -> Stream m Int 31 | --source n = S.fromList [n..n+value] 32 | source n = S.unfoldrM step n 33 | where 34 | step cnt = 35 | if cnt > n + value 36 | then return Nothing 37 | else return (Just (cnt, cnt + 1)) 38 | {- 39 | source n = S.unfoldr step n 40 | where 41 | step cnt = 42 | if cnt > n + value 43 | then Nothing 44 | else (Just (cnt, cnt + 1)) 45 | -} 46 | 47 | {-# INLINE sourceN #-} 48 | sourceN :: Monad m => Int -> Int -> Stream m Int 49 | sourceN count begin = S.unfoldrM step begin 50 | where 51 | step i = 52 | if i > begin + count 53 | then return Nothing 54 | else return (Just (i, i + 1)) 55 | 56 | {-# INLINE sourceIntFromThenTo #-} 57 | sourceIntFromThenTo :: Monad m => Int -> Stream m Int 58 | sourceIntFromThenTo n = S.enumFromStepN n 1 value 59 | 60 | ------------------------------------------------------------------------------- 61 | -- Append 62 | ------------------------------------------------------------------------------- 63 | 64 | {-# INLINE appendSourceR #-} 65 | appendSourceR :: Int -> P.IO () 66 | appendSourceR n = 67 | toNull $ P.foldr (S.++) S.empty (P.map S.singleton [n..n+appendValue]) 68 | 69 | {-# INLINE appendSourceL #-} 70 | appendSourceL :: Int -> P.IO () 71 | appendSourceL n = 72 | toNull $ P.foldl (S.++) S.empty (P.map S.singleton [n..n+appendValue]) 73 | 74 | ------------------------------------------------------------------------------- 75 | -- Elimination 76 | ------------------------------------------------------------------------------- 77 | 78 | {-# INLINE runStream #-} 79 | runStream :: Monad m => Stream m a -> m () 80 | runStream = S.mapM_ (\_ -> return ()) 81 | 82 | {-# INLINE toNull #-} 83 | {-# INLINE toList #-} 84 | {-# INLINE foldl #-} 85 | {-# INLINE last #-} 86 | toNull :: Monad m => Stream m Int -> m () 87 | toList :: Monad m => Stream m Int -> m [Int] 88 | foldl :: Monad m => Stream m Int -> m Int 89 | last :: Monad m => Stream m Int -> m Int 90 | 91 | toNull = runStream 92 | toList = S.toList 93 | foldl = S.foldl' (+) 0 94 | last = S.last 95 | 96 | ------------------------------------------------------------------------------- 97 | -- Transformation 98 | ------------------------------------------------------------------------------- 99 | 100 | {-# INLINE transform #-} 101 | transform :: Monad m => Stream m a -> m () 102 | transform = runStream 103 | 104 | {-# INLINE composeN #-} 105 | composeN 106 | :: Monad m 107 | => Int -> (Stream m Int -> Stream m Int) -> Stream m Int -> m () 108 | composeN n f = 109 | case n of 110 | 1 -> transform . f 111 | 2 -> transform . f . f 112 | 3 -> transform . f . f . f 113 | 4 -> transform . f . f . f . f 114 | _ -> undefined 115 | 116 | {-# INLINE scan #-} 117 | {-# INLINE map #-} 118 | {-# INLINE mapM #-} 119 | {-# INLINE filterEven #-} 120 | {-# INLINE filterAllOut #-} 121 | {-# INLINE filterAllIn #-} 122 | {-# INLINE takeOne #-} 123 | {-# INLINE takeAll #-} 124 | {-# INLINE takeWhileTrue #-} 125 | {-# INLINE dropOne #-} 126 | {-# INLINE dropAll #-} 127 | {-# INLINE dropWhileTrue #-} 128 | {-# INLINE dropWhileFalse #-} 129 | scan, map, mapM, 130 | filterEven, filterAllOut, filterAllIn, 131 | takeOne, takeAll, takeWhileTrue, 132 | dropOne, dropAll, dropWhileTrue, dropWhileFalse 133 | :: Monad m => Int -> Stream m Int -> m () 134 | 135 | scan n = composeN n $ S.scanl' (+) 0 136 | map n = composeN n $ S.map (+1) 137 | mapM n = composeN n $ S.mapM return 138 | filterEven n = composeN n $ S.filter even 139 | filterAllOut n = composeN n $ S.filter (> maxValue) 140 | filterAllIn n = composeN n $ S.filter (<= maxValue) 141 | takeOne n = composeN n $ S.take 1 142 | takeAll n = composeN n $ S.take maxValue 143 | takeWhileTrue n = composeN n $ S.takeWhile (<= maxValue) 144 | dropOne n = composeN n $ S.drop 1 145 | dropAll n = composeN n $ S.drop maxValue 146 | dropWhileFalse n = composeN n $ S.dropWhile (> maxValue) 147 | dropWhileTrue n = composeN n $ S.dropWhile (<= maxValue) 148 | 149 | ------------------------------------------------------------------------------- 150 | -- Iteration 151 | ------------------------------------------------------------------------------- 152 | 153 | iterStreamLen, maxIters :: Int 154 | iterStreamLen = 10 155 | maxIters = 100000 156 | 157 | {-# INLINE iterateSource #-} 158 | iterateSource 159 | :: Monad m 160 | => (Stream m Int -> Stream m Int) -> Int -> Int -> Stream m Int 161 | iterateSource g i n = f i (sourceN iterStreamLen n) 162 | where 163 | f (0 :: Int) m = g m 164 | f x m = g (f (x P.- 1) m) 165 | 166 | {-# INLINE iterateMapM #-} 167 | {-# INLINE iterateScan #-} 168 | {-# INLINE iterateFilterEven #-} 169 | {-# INLINE iterateTakeAll #-} 170 | {-# INLINE iterateDropOne #-} 171 | {-# INLINE iterateDropWhileFalse #-} 172 | {-# INLINE iterateDropWhileTrue #-} 173 | iterateMapM, iterateScan, iterateFilterEven, iterateTakeAll, iterateDropOne, 174 | iterateDropWhileFalse, iterateDropWhileTrue :: Monad m => Int -> Stream m Int 175 | 176 | -- Scan increases the size of the stream by 1, drop 1 to not blow up the size 177 | -- due to many iterations. 178 | iterateScan n = iterateSource (S.drop 1 . S.scanl' (+) 0) maxIters n 179 | iterateMapM n = iterateSource (S.mapM return) maxIters n 180 | iterateFilterEven n = iterateSource (S.filter even) maxIters n 181 | iterateTakeAll n = iterateSource (S.take maxValue) maxIters n 182 | iterateDropOne n = iterateSource (S.drop 1) maxIters n 183 | iterateDropWhileFalse n = iterateSource (S.dropWhile (> maxValue)) maxIters n 184 | iterateDropWhileTrue n = iterateSource (S.dropWhile (<= maxValue)) maxIters n 185 | 186 | ------------------------------------------------------------------------------- 187 | -- Mixed Composition 188 | ------------------------------------------------------------------------------- 189 | 190 | {-# INLINE scanMap #-} 191 | {-# INLINE dropMap #-} 192 | {-# INLINE dropScan #-} 193 | {-# INLINE takeDrop #-} 194 | {-# INLINE takeScan #-} 195 | {-# INLINE takeMap #-} 196 | {-# INLINE filterDrop #-} 197 | {-# INLINE filterTake #-} 198 | {-# INLINE filterScan #-} 199 | {-# INLINE filterMap #-} 200 | scanMap, dropMap, dropScan, takeDrop, takeScan, takeMap, filterDrop, 201 | filterTake, filterScan, filterMap 202 | :: Monad m => Int -> Stream m Int -> m () 203 | 204 | scanMap n = composeN n $ S.map (subtract 1) . S.scanl' (+) 0 205 | dropMap n = composeN n $ S.map (subtract 1) . S.drop 1 206 | dropScan n = composeN n $ S.scanl' (+) 0 . S.drop 1 207 | takeDrop n = composeN n $ S.drop 1 . S.take maxValue 208 | takeScan n = composeN n $ S.scanl' (+) 0 . S.take maxValue 209 | takeMap n = composeN n $ S.map (subtract 1) . S.take maxValue 210 | filterDrop n = composeN n $ S.drop 1 . S.filter (<= maxValue) 211 | filterTake n = composeN n $ S.take maxValue . S.filter (<= maxValue) 212 | filterScan n = composeN n $ S.scanl' (+) 0 . S.filter (<= P.maxBound) 213 | filterMap n = composeN n $ S.map (subtract 1) . S.filter (<= maxValue) 214 | 215 | ------------------------------------------------------------------------------- 216 | -- Zipping and concat 217 | ------------------------------------------------------------------------------- 218 | 219 | {-# INLINE zip #-} 220 | zip :: Monad m => Stream m Int -> m () 221 | zip src = transform $ (S.zipWith (,) src src) 222 | 223 | {-# INLINE concatMap #-} 224 | concatMap :: Monad m => Stream m Int -> m () 225 | concatMap src = transform $ (S.concatMap (S.replicate 3) src) 226 | 227 | main :: P.IO () 228 | main = $(defaultMain "VectorStreams") 229 | -------------------------------------------------------------------------------- /Benchmarks/VectorUnboxed.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : Benchmarks.Vector 3 | -- Copyright : (c) 2018 Harendra Kumar 4 | -- 5 | -- License : MIT 6 | -- Maintainer : harendra.kumar@gmail.com 7 | 8 | {-# LANGUAGE CPP #-} 9 | {-# LANGUAGE ScopedTypeVariables #-} 10 | {-# LANGUAGE TemplateHaskell #-} 11 | 12 | module Benchmarks.VectorUnboxed where 13 | 14 | import Benchmarks.DefaultMain (defaultMain) 15 | #define VECTOR_UNBOXED 16 | #include "VectorCommon.hs" 17 | 18 | main :: P.IO () 19 | main = $(defaultMain "VectorUnboxed") 20 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | ## 0.4.0 2 | 3 | * Add different streaming libraries under individual flags 4 | * Use the external bench-report package and remove the shell scripts 5 | * User streamly-core package (streamly-0.9.0 changes) 6 | 7 | ## 0.3.0 8 | 9 | * Simplify the README, use text tables instead of graphs. 10 | * Upgrade to latest release of all streaming packages 11 | * Build bench-report utility independently to avoid dependency issues 12 | * Add benchmarks for streamly pure lists, streamly arrays, bytestring, 13 | text, dlist, sequence 14 | * Add benchmarks to measure composition of the filtering and 15 | transformation operations multiple times 16 | * Add benchmarks to measure composition of various combinations of different 17 | operations multiple times. 18 | * Add benchmarks that iterate the same operation multiple times 19 | * Use the `bench-show` package for better reporting of diffs. Supports 20 | comparison in multiples or percentages of other packages. 21 | 22 | ## 0.2.0 23 | 24 | * Added benchmarks for pure lists 25 | * Added benchmarks for pure `vector` 26 | * Added benchmarks for `vector` monadic streaming library 27 | * Added `drinkery` streaming library 28 | * The code is modular now, package specific ops for each benchmarked package 29 | are contained in a separate own module. It is much easier to add a new 30 | package now. 31 | * The benchmarking code now works for `IO` as well as `Identity` monad. 32 | * Used the same stream generation method for all libraries for a fair 33 | comparison. 34 | * Use a monadic API (`unfoldrM`) for generating the stream. 35 | * conduit-1.3.0 has a performance issue with `mapM_`. Avoided using `mapM_` and 36 | used `sinkNull` instead. See https://github.com/snoyberg/conduit/issues/363. 37 | This workaround improves the performance of all conduit benchmarks that drain 38 | the stream. 39 | * pipes also had an issue similar to that of conduit. The code was using 40 | `mapM_` which was very inefficient, used `discard` instead and got a 41 | significant boost in numbers. 42 | 43 | ## 0.1.0 44 | 45 | * Initial release 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2017 Harendra Kumar 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | Please also see the licenses for other contributions in the "licenses" 21 | directory. 22 | 23 | ------------------------------------------------------------------------------- 24 | -- The initial code was adapated from the "machines" package 25 | ------------------------------------------------------------------------------- 26 | 27 | Copyright 2012-2015 Edward Kmett, Runar Bjarnason, Paul Chiusano 28 | 29 | All rights reserved. 30 | 31 | Redistribution and use in source and binary forms, with or without 32 | modification, are permitted provided that the following conditions 33 | are met: 34 | 35 | 1. Redistributions of source code must retain the above copyright 36 | notice, this list of conditions and the following disclaimer. 37 | 38 | 2. Redistributions in binary form must reproduce the above copyright 39 | notice, this list of conditions and the following disclaimer in the 40 | documentation and/or other materials provided with the distribution. 41 | 42 | 3. Neither the name of the author nor the names of his contributors 43 | may be used to endorse or promote products derived from this software 44 | without specific prior written permission. 45 | 46 | THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR 47 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 48 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 49 | DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR 50 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 51 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 52 | OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 53 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 54 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 55 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 56 | POSSIBILITY OF SUCH DAMAGE. 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Streaming Benchmarks 2 | 3 | [![Hackage](https://img.shields.io/hackage/v/streaming-benchmarks.svg?style=flat)](https://hackage.haskell.org/package/streaming-benchmarks) 4 | [![Gitter chat](https://badges.gitter.im/composewell/gitter.svg)](https://gitter.im/composewell/streamly) 5 | [![Build Status](https://travis-ci.org/composewell/streaming-benchmarks.svg?branch=master)](https://travis-ci.org/composewell/streaming-benchmarks) 6 | [![Windows Build status](https://ci.appveyor.com/api/projects/status/8d1kgrrw9mmxv5xt?svg=true)](https://ci.appveyor.com/project/harendra-kumar/streaming-benchmarks) 7 | 8 | This package provides micro-benchmarks to measure and compare the 9 | performance of various streaming implementations in Haskell. 10 | 11 | We have taken due to care to make sure that we are 12 | benchmarking correctly and fairly. See [the notes on correct 13 | benchmarking](docs/benchmarking-notes.md). 14 | 15 | DISCLAIMER: This package is a result of benchmarking effort done during the 16 | development of [streamly](https://github.com/composewell/streamly) by the 17 | authors of [streamly](https://github.com/composewell/streamly). 18 | 19 | ## Benchmarks 20 | 21 | The benchmark names are obvious, some of them are described below. Single 22 | operation benchmarks: 23 | 24 | | Name | Description | 25 | | -------------- | ----------------------------------------------------------- | 26 | | `drain` | Just discards all the elements in the stream | 27 | | `drop-all` | drops all element using the ``drop`` operation | 28 | | `last` | extract the last element of the stream | 29 | | `fold` | sum all the numbers in the stream | 30 | | `map` | increments each number in the stream by 1 | 31 | | `take-all` | Use ``take`` to retain all the elements in the stream | 32 | | `filter-even` | Keep even numbers, discard odd | 33 | | `scan` | scan the stream using ``+`` operation | 34 | | `mapM` | transform the stream using a monadic action | 35 | | `zip` | combines corresponding elements of the two streams together | 36 | 37 | Composite operation benchmarks: 38 | 39 | | Name | Description | 40 | | -------------- | ----------------------------------------------------------- | 41 | | `map x 4` | perform `map` operation 4 times | 42 | | `take-map` | `take` followed by a `map` | 43 | 44 | For more details on how each benchmark is implemented see 45 | [this benchmark file](https://github.com/composewell/streaming-benchmarks/blob/master/Benchmarks/Streamly.hs). 46 | 47 | Each benchmark is run in a separate process to avoid any effects of GC 48 | interference and sharing across benchmarks. 49 | 50 | ## Benchmark Results 51 | 52 | Below we present some results comparing 53 | [streamly](https://github.com/composewell/streamly) with other streaming 54 | implementations. 55 | Due care has been taken to keep the comparisons fair. We have optimized 56 | each library's code to the best of our knowledge, please point out if 57 | you find any measurement issues. 58 | 59 | ## Running Benchmarks 60 | 61 | You can run individual benchmarks using `cabal bench `. You may 62 | need to specify a build flag to include a particular streaming library 63 | e.g. `--flag pipes` to benchmark the `pipes` library. Please consult the cabal 64 | file to find the exact flag names. 65 | 66 | ### Generating Comparison Reports 67 | 68 | You can generate the comparison reports presented in this page yourself. 69 | To do so you need to run the benchmarks using the reporting tool, first build 70 | the reporting tool using the following command: 71 | 72 | ``` 73 | $ cd bench-runner 74 | $ cabal install --project-file cabal.project.user --installdir ../bin 75 | ``` 76 | 77 | If you want to create a report for benchmarks showing a 10% or greater 78 | improvement with Streamly over Lists, use: 79 | 80 | ``` 81 | $ bin/bench-runner --package-name streaming-benchmarks --package-version 0.4.0 --compare --diff-cutoff-percent 10 --diff-style absolute --targets "StreamlyPure List" 82 | ``` 83 | 84 | After running once, you can add `--no-measure` option to use the same benchmark 85 | measurements for different reports. For example, use: 86 | 87 | * `--diff-cutoff-percent -10` to know where lists are better than streamly 88 | * `--diff-style multiples` to generate ratios instead of absolute values 89 | 90 | ### Streamly vs Haskell Lists 91 | 92 | Streamly, when used with `Identity` monad, is almost the same as Haskell lists 93 | (in the `base` package). 94 | [See this](https://github.com/composewell/streamly/blob/master/docs/streamly-vs-lists.md) 95 | for more details. 96 | 97 | The following table compares the timing of several operations for 98 | [streamly](https://github.com/composewell/streamly) 99 | with lists using a one million element stream. For brevity only 100 | those operations where the performance of the two packages differ by 101 | more than 10% are shown in the table below. The last column shows how 102 | many times slower list is compared to streamly. 103 | 104 | | Benchmark | streamly(μs) | list(μs) | list/streamly | 105 | | ------------------- | ------------ | ---------- | ------------- | 106 | | drop-map x 4 | 375.09 | 76925.32 | 205.08 | 107 | | filter-drop x 4 | 382.03 | 54848.54 | 143.57 | 108 | | drop-scan x 4 | 795.81 | 76716.79 | 96.40 | 109 | | filter-scan x 4 | 795.60 | 44559.15 | 56.01 | 110 | | scan-map x 4 | 1192.19 | 48838.22 | 40.97 | 111 | | take-map x 4 | 1500.99 | 60126.58 | 40.06 | 112 | | filter-take x 4 | 1502.01 | 48766.87 | 32.47 | 113 | | take-drop x 4 | 1499.62 | 41720.03 | 27.82 | 114 | | take-scan x 4 | 1874.94 | 51283.30 | 27.35 | 115 | | drop-one x 4 | 375.33 | 8993.87 | 23.96 | 116 | | dropWhile-false x 4 | 374.61 | 8957.79 | 23.91 | 117 | | dropWhile-false | 374.83 | 8670.05 | 23.13 | 118 | | drop-one | 390.77 | 8681.85 | 22.22 | 119 | | dropWhile-true | 571.60 | 12237.48 | 21.41 | 120 | | drop-all | 562.94 | 8262.38 | 14.68 | 121 | | take-all | 624.83 | 564.34 | 1/1.11 | 122 | | scan x 4 | 795.83 | 385.85 | 1/2.06 | 123 | | appendR[10000] | 360.75 | 126.95 | 1/2.84 | 124 | | concatMap | 34957.71 | 1124.85 | 1/31.08 | 125 | 126 | * streamly-0.8.0, base-4.14.1.0, ghc-8.10.4, Linux 127 | 128 | To generate these reports run `bench-runner` with: 129 | 130 | * `--targets "StreamlyPure List"` 131 | 132 | ### Streamly vs Streaming 133 | 134 | The following table compares the timing of several operations 135 | for [streamly](https://github.com/composewell/streamly) with 136 | [streaming](https://hackage.haskell.org/package/streaming) using a 137 | million element stream. 138 | 139 | | Benchmark | streamly(μs) | streaming(μs) | streaming/streamly | 140 | | ------------------- | ------------ | ------------- | ------------------ | 141 | | appendR[10000] | 326.56 | 1301176.69 | 3984.54 | 142 | | mapM x 4 | 374.42 | 223591.08 | 597.17 | 143 | | filter-map x 4 | 381.07 | 194903.88 | 511.47 | 144 | | filter-scan x 4 | 795.66 | 233527.90 | 293.50 | 145 | | filter-all-in x 4 | 375.40 | 102629.64 | 273.38 | 146 | | filter-drop x 4 | 387.15 | 99096.98 | 255.96 | 147 | | map x 4 | 386.49 | 94944.87 | 245.66 | 148 | | drop-map x 4 | 375.62 | 89669.37 | 238.73 | 149 | | scan x 4 | 797.00 | 166332.40 | 208.70 | 150 | | scan-map x 4 | 1194.30 | 238804.48 | 199.95 | 151 | | filter-even x 4 | 396.37 | 77865.47 | 196.45 | 152 | | drop-scan x 4 | 796.98 | 156063.52 | 195.82 | 153 | | takeWhile-true x 4 | 562.49 | 90183.53 | 160.33 | 154 | | scan | 375.24 | 47520.57 | 126.64 | 155 | | filter-take x 4 | 1498.55 | 189635.34 | 126.55 | 156 | | mapM | 388.10 | 46689.61 | 120.30 | 157 | | take-map x 4 | 1500.71 | 178954.50 | 119.25 | 158 | | zip | 656.65 | 66689.73 | 101.56 | 159 | | take-scan x 4 | 2380.35 | 241675.75 | 101.53 | 160 | | filter-all-in | 375.97 | 33590.14 | 89.34 | 161 | | map | 375.02 | 33081.13 | 88.21 | 162 | | filter-even | 393.26 | 30458.46 | 77.45 | 163 | | filter-all-out | 382.87 | 26826.21 | 70.07 | 164 | | take-all x 4 | 1499.71 | 101332.53 | 67.57 | 165 | | take-drop x 4 | 1498.53 | 98281.99 | 65.59 | 166 | | takeWhile-true | 562.62 | 31863.25 | 56.63 | 167 | | foldl' | 388.22 | 18503.15 | 47.66 | 168 | | drop-all | 562.08 | 25200.32 | 44.83 | 169 | | take-all | 768.65 | 33247.97 | 43.26 | 170 | | dropWhile-true | 564.87 | 24431.50 | 43.25 | 171 | | last | 385.53 | 15240.85 | 39.53 | 172 | | dropWhile-false | 374.83 | 14566.70 | 38.86 | 173 | | drop-one | 374.80 | 14565.01 | 38.86 | 174 | | drop-one x 4 | 375.88 | 14448.67 | 38.44 | 175 | | dropWhile-false x 4 | 390.12 | 14619.42 | 37.47 | 176 | | drain | 375.06 | 13702.29 | 36.53 | 177 | | toList | 117708.83 | 201444.81 | 1.71 | 178 | 179 | * streamly-0.8.0, streaming-0.2.3.0, ghc-8.10.4, Linux 180 | 181 | To generate these reports run `bench-runner` with: 182 | 183 | * `--targets "Streamly Streaming" --cabal-build-options "--flag streaming"` 184 | 185 | ### Streamly vs Pipes 186 | 187 | The following table compares the timing of several operations 188 | for [streamly](https://github.com/composewell/streamly) with 189 | [pipes](https://hackage.haskell.org/package/pipes) using a 190 | million element stream. 191 | 192 | | Benchmark | streamly(μs) | pipes(μs) | pipes/streamly | 193 | | ------------------- | ------------ | --------- | -------------- | 194 | | appendR[10000] | 327.90 | 901135.92 | 2748.21 | 195 | | mapM x 4 | 375.20 | 407184.39 | 1085.23 | 196 | | filter-map x 4 | 381.52 | 366759.70 | 961.31 | 197 | | drop-map x 4 | 375.48 | 281296.82 | 749.16 | 198 | | filter-all-in x 4 | 375.60 | 222331.68 | 591.93 | 199 | | filter-drop x 4 | 387.44 | 222830.71 | 575.14 | 200 | | drop-scan x 4 | 797.23 | 336737.89 | 422.39 | 201 | | filter-even x 4 | 389.87 | 152688.91 | 391.64 | 202 | | filter-scan x 4 | 797.38 | 309733.91 | 388.44 | 203 | | drop-one x 4 | 375.48 | 139851.13 | 372.46 | 204 | | map x 4 | 386.56 | 136289.32 | 352.57 | 205 | | dropWhile-false x 4 | 390.72 | 137395.44 | 351.65 | 206 | | scan-map x 4 | 1194.38 | 381286.88 | 319.23 | 207 | | takeWhile-true x 4 | 562.86 | 165143.23 | 293.40 | 208 | | scan x 4 | 796.68 | 222986.17 | 279.90 | 209 | | mapM | 388.19 | 95576.97 | 246.21 | 210 | | filter-all-in | 375.21 | 71297.42 | 190.02 | 211 | | take-map x 4 | 1502.76 | 275887.24 | 183.59 | 212 | | scan | 374.81 | 65549.13 | 174.89 | 213 | | take-drop x 4 | 1503.43 | 256448.45 | 170.58 | 214 | | filter-even | 390.29 | 66183.72 | 169.57 | 215 | | filter-all-out | 376.99 | 59074.54 | 156.70 | 216 | | drop-one | 375.19 | 58395.24 | 155.64 | 217 | | dropWhile-false | 375.35 | 58223.03 | 155.12 | 218 | | map | 375.05 | 57736.43 | 153.94 | 219 | | filter-take x 4 | 1503.00 | 227925.71 | 151.65 | 220 | | take-scan x 4 | 2455.91 | 354284.33 | 144.26 | 221 | | zip | 657.07 | 86011.93 | 130.90 | 222 | | takeWhile-true | 564.14 | 61390.21 | 108.82 | 223 | | take-all x 4 | 1502.32 | 139730.70 | 93.01 | 224 | | dropWhile-true | 564.03 | 49227.19 | 87.28 | 225 | | drop-all | 562.05 | 46505.37 | 82.74 | 226 | | take-all | 824.09 | 60511.34 | 73.43 | 227 | | drain | 375.29 | 26390.59 | 70.32 | 228 | | foldl' | 397.34 | 19064.05 | 47.98 | 229 | | last | 387.11 | 17364.44 | 44.86 | 230 | | toList | 117257.09 | 207405.94 | 1.77 | 231 | 232 | * streamly-0.8.0, pipes-4.3.16, ghc-8.10.4, Linux 233 | 234 | To generate these reports run `bench-runner` with: 235 | 236 | * `--targets "Streamly Pipes" --cabal-build-options "--flag pipes"` 237 | 238 | ### Streamly vs Conduit 239 | 240 | The following table compares the timing of several operations 241 | for [streamly](https://github.com/composewell/streamly) with 242 | [conduit](https://hackage.haskell.org/package/conduit) using a 243 | million element stream. 244 | 245 | | Benchmark | streamly(μs) | conduit(μs) | conduit/streamly | 246 | | ------------------- | ------------ | ----------- | ---------------- | 247 | | mapM x 4 | 375.46 | 297002.31 | 791.04 | 248 | | filter-map x 4 | 380.79 | 267543.81 | 702.60 | 249 | | drop-map x 4 | 375.66 | 232307.84 | 618.39 | 250 | | filter-drop x 4 | 386.05 | 235029.15 | 608.81 | 251 | | filter-scan x 4 | 796.56 | 306556.67 | 384.85 | 252 | | drop-scan x 4 | 797.19 | 300789.06 | 377.31 | 253 | | zip | 657.29 | 210069.05 | 319.60 | 254 | | filter-all-in x 4 | 375.24 | 118506.68 | 315.82 | 255 | | scan-map x 4 | 1194.67 | 360671.18 | 301.90 | 256 | | map x 4 | 387.00 | 113497.14 | 293.27 | 257 | | drop-one x 4 | 375.49 | 101842.95 | 271.23 | 258 | | dropWhile-false x 4 | 389.44 | 102051.22 | 262.04 | 259 | | scan x 4 | 796.72 | 190479.35 | 239.08 | 260 | | takeWhile-true x 4 | 564.58 | 114459.57 | 202.73 | 261 | | filter-even x 4 | 391.76 | 72369.30 | 184.73 | 262 | | filter-take x 4 | 1502.04 | 267921.27 | 178.37 | 263 | | take-map x 4 | 1502.88 | 238875.95 | 158.95 | 264 | | take-drop x 4 | 1500.34 | 232606.19 | 155.04 | 265 | | take-scan x 4 | 2443.83 | 309738.86 | 126.74 | 266 | | mapM | 389.15 | 41897.48 | 107.66 | 267 | | scan | 375.40 | 38137.85 | 101.59 | 268 | | take-all x 4 | 1502.32 | 110682.74 | 73.67 | 269 | | filter-all-in | 375.31 | 26024.21 | 69.34 | 270 | | dropWhile-false | 375.10 | 25307.13 | 67.47 | 271 | | map | 375.18 | 23088.09 | 61.54 | 272 | | drop-one | 375.43 | 22020.65 | 58.65 | 273 | | filter-even | 392.28 | 21504.28 | 54.82 | 274 | | takeWhile-true | 562.79 | 29012.68 | 51.55 | 275 | | filter-all-out | 378.76 | 15736.05 | 41.55 | 276 | | drop-all | 562.89 | 19916.48 | 35.38 | 277 | | foldl' | 388.88 | 12499.03 | 32.14 | 278 | | dropWhile-true | 564.43 | 17983.35 | 31.86 | 279 | | take-all | 784.67 | 24425.36 | 31.13 | 280 | | last | 385.75 | 10974.84 | 28.45 | 281 | | drain | 375.18 | 4272.15 | 11.39 | 282 | | appendR[10000] | 326.93 | 1207.88 | 3.69 | 283 | | toList | 116441.26 | 199138.09 | 1.71 | 284 | 285 | * streamly-0.8.0, conduit-1.3.4.1, ghc-8.10.4, Linux 286 | 287 | To generate these reports run `bench-runner` with: 288 | 289 | * `--targets "Streamly Conduit" --cabal-build-options "--flag conduit"` 290 | 291 | ## Stack and heap utilization 292 | 293 | `bench-runner` also generates a `maxrss` comparison report, displaying the 294 | maximum resident memory for each benchmark. 295 | 296 | ## Comparing other libraries 297 | 298 | This package supports many streaming libraries, bytestring, text, vector etc. 299 | You can run the benchmarks directly or use `bench-runner` to create comparison 300 | reports. Check out the targets in the cabal file. 301 | 302 | ## Adding New Libraries 303 | 304 | It is trivial to add a new package. This is how 305 | [a benchmark file](https://github.com/composewell/streaming-benchmarks/blob/master/Benchmarks/Streamly.hs) 306 | for a streaming package looks like. Pull requests are welcome, we will be happy 307 | to help, [just join the gitter chat](https://gitter.im/composewell/streamly) 308 | and ask! 309 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # packcheck-0.4.2 2 | # You can use any of the options supported by packcheck as environment 3 | # variables here. See https://github.com/composewell/packcheck for all 4 | # options and their explanation. 5 | 6 | environment: 7 | # ------------------------------------------------------------------------ 8 | # Global options, you can use these per build as well 9 | # ------------------------------------------------------------------------ 10 | global: 11 | # ------------------------------------------------------------------------ 12 | # Common options 13 | # ------------------------------------------------------------------------ 14 | # GHC_OPTIONS: "-Werror" 15 | CABAL_REINIT_CONFIG: "y" 16 | LC_ALL: "C.UTF-8" 17 | 18 | # ------------------------------------------------------------------------ 19 | # What to build 20 | # ------------------------------------------------------------------------ 21 | # DISABLE_TEST: "y" 22 | # DISABLE_BENCH: "y" 23 | # DISABLE_DOCS: "y" 24 | DISABLE_SDIST_BUILD: "y" 25 | # DISABLE_DIST_CHECKS: "y" 26 | 27 | # ------------------------------------------------------------------------ 28 | # stack options 29 | # ------------------------------------------------------------------------ 30 | # Note requiring a specific version of stack using STACKVER may fail due to 31 | # github API limit while checking and upgrading/downgrading to the specific 32 | # version. 33 | #STACKVER: "1.6.5" 34 | STACK_UPGRADE: "y" 35 | RESOLVER: "lts-21.25" 36 | STACK_ROOT: "c:\\sr" 37 | 38 | # ------------------------------------------------------------------------ 39 | # cabal options 40 | # ------------------------------------------------------------------------ 41 | CABAL_CHECK_RELAX: "y" 42 | CABAL_NO_SANDBOX: "y" 43 | CABAL_HACKAGE_MIRROR: "hackage.haskell.org:http://hackage.fpcomplete.com" 44 | 45 | # ------------------------------------------------------------------------ 46 | # Where to find the required tools 47 | # ------------------------------------------------------------------------ 48 | PATH: "%PATH%;%APPDATA%\\local\\bin" 49 | LOCAL_BIN: "%APPDATA%\\local\\bin" 50 | 51 | # ------------------------------------------------------------------------ 52 | # Location of packcheck.sh (the shell script invoked to perform CI tests ). 53 | # ------------------------------------------------------------------------ 54 | # You can either commit the packcheck.sh script at this path in your repo or 55 | # you can use it by specifying the PACKCHECK_REPO_URL option below in which 56 | # case it will be automatically copied from the packcheck repo to this path 57 | # during CI tests. In any case it is finally invoked from this path. 58 | PACKCHECK_LOCAL_PATH: "./packcheck.sh" 59 | # If you have not committed packcheck.sh in your repo at PACKCHECK_LOCAL_PATH 60 | # then it is automatically pulled from this URL. 61 | PACKCHECK_GITHUB_URL: "https://raw.githubusercontent.com/composewell/packcheck" 62 | PACKCHECK_GITHUB_COMMIT: "79fb4437009a7ebdada33d0493c27ee30160ec3f" 63 | 64 | # Override the temp directory to avoid sed escaping issues 65 | # See https://github.com/haskell/cabal/issues/5386 66 | TMP: "c:\\tmp" 67 | 68 | cache: 69 | - "%STACK_ROOT%" 70 | - "%LOCAL_BIN%" 71 | - "%APPDATA%\\cabal" 72 | - "%APPDATA%\\ghc" 73 | # - "%LOCALAPPDATA%\\Programs\\stack" 74 | 75 | clone_folder: "c:\\pkg" 76 | build: off 77 | 78 | before_test: 79 | - if not exist %PACKCHECK_LOCAL_PATH% curl -sSkL -o%PACKCHECK_LOCAL_PATH% %PACKCHECK_GITHUB_URL%/%PACKCHECK_GITHUB_COMMIT%/packcheck.sh 80 | - if not exist %LOCAL_BIN% mkdir %LOCAL_BIN% 81 | - where stack.exe || curl -sSkL -ostack.zip http://www.stackage.org/stack/windows-x86_64 && 7z x stack.zip stack.exe && move stack.exe %LOCAL_BIN% 82 | - if defined STACKVER (stack upgrade --binary-only --binary-version %STACKVER%) else (stack upgrade --binary-only || ver > nul) 83 | - stack --version 84 | 85 | test_script: 86 | - stack setup > nul 87 | - for /f "usebackq tokens=*" %%i in (`where 7z.exe`) do set PATH7Z=%%i\.. 88 | - for /f "usebackq tokens=*" %%i in (`where git.exe`) do set PATHGIT=%%i\.. 89 | - chcp 65001 && stack exec bash -- -c "chmod +x %PACKCHECK_LOCAL_PATH%; %PACKCHECK_LOCAL_PATH% stack PATH=/usr/bin:\"%PATH7Z%\":\"%PATHGIT%\"" 90 | -------------------------------------------------------------------------------- /bench-runner/Main.hs: -------------------------------------------------------------------------------- 1 | module Main (main) where 2 | 3 | import BenchRunner (mainWith) 4 | import BuildLib (Quickness(..)) 5 | import Control.Applicative ((<|>)) 6 | import Data.List (isInfixOf, isPrefixOf, isSuffixOf) 7 | 8 | -- tags with _grp and _cmp suffixes are special 9 | -- [(", [])] 10 | targets :: [(String, [String])] 11 | targets = 12 | [ ("StreamlyPure", ["pure_stream_cmp"]) 13 | , ("List", ["pure_stream_cmp"]) 14 | , ("Streamly", ["io_stream_cmp"]) 15 | , ("ByteStringLazy", ["io_stream_cmp"]) 16 | , ("VectorStreams", []) 17 | -- , ("Streaming", []) 18 | -- , ("Machines", []) 19 | -- , ("Pipes", []) 20 | -- , ("Conduit", []) 21 | -- , ("Drinkery", []) 22 | ] 23 | 24 | rtsOpts :: String -> String -> String 25 | rtsOpts exeName benchName0 = unwords [general, exeSpecific, benchSpecific] 26 | 27 | where 28 | 29 | -- Drop All. 30 | benchName = drop 4 benchName0 31 | general 32 | -- | "o-1-sp" `isInfixOf` benchName = "-K36K -M16M" 33 | | otherwise = "" 34 | exeSpecific 35 | -- | "Prelude.Concurrent" `isSuffixOf` exeName = "-K512K -M384M" 36 | | otherwise = "" 37 | benchSpecific 38 | -- | "Data.Stream.StreamD/o-n-space.elimination.toList" == benchName = "-K2M" 39 | | otherwise = "" 40 | 41 | speedOpts :: String -> String -> Maybe Quickness 42 | speedOpts exeName benchName0 = exeSpecific <|> benchSpecific 43 | 44 | where 45 | 46 | -- slowestOf Quicker _ = Quicker 47 | -- slowestOf _ Quicker = Quicker 48 | -- slowestOf _ _ = SuperQuick 49 | 50 | -- Drop All. 51 | benchName = drop 4 benchName0 52 | exeSpecific 53 | -- | "Prelude.Concurrent" == exeName = Just SuperQuick 54 | | otherwise = Nothing 55 | benchSpecific 56 | -- | "Prelude.Parallel/o-n-heap.mapping.mapM" == benchName = Just SuperQuick 57 | | otherwise = Nothing 58 | 59 | main :: IO () 60 | main = mainWith targets speedOpts rtsOpts 61 | -------------------------------------------------------------------------------- /bench-runner/bench-runner.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 2.4 2 | name: bench-runner 3 | version: 0.1.0.0 4 | 5 | -- A short (one-line) description of the package. 6 | -- synopsis: 7 | 8 | -- A longer description of the package. 9 | -- description: 10 | 11 | -- A URL where users can report bugs. 12 | -- bug-reports: 13 | 14 | -- The license under which the package is released. 15 | -- license: 16 | 17 | -- The package author(s). 18 | -- author: 19 | 20 | -- An email address to which users can send suggestions, bug reports, and patches. 21 | -- maintainer: 22 | 23 | -- A copyright notice. 24 | -- copyright: 25 | -- category: 26 | -- extra-source-files: CHANGELOG.md 27 | 28 | executable bench-runner 29 | ghc-options: -Wall 30 | main-is: Main.hs 31 | 32 | -- Modules included in this executable, other than Main. 33 | -- other-modules: 34 | 35 | -- LANGUAGE extensions used by modules in this package. 36 | -- other-extensions: 37 | build-depends: base 38 | , bench-report 39 | , containers 40 | hs-source-dirs: . 41 | default-language: Haskell2010 42 | -------------------------------------------------------------------------------- /bench-runner/cabal.project.user: -------------------------------------------------------------------------------- 1 | packages: . 2 | 3 | source-repository-package 4 | type: git 5 | location: https://github.com/composewell/streamly.git 6 | tag: 9a039b89c6b81300b2bd9c8765bf4bfa18e7c83c 7 | 8 | source-repository-package 9 | type: git 10 | location: https://github.com/composewell/streamly.git 11 | tag: 9a039b89c6b81300b2bd9c8765bf4bfa18e7c83c 12 | subdir: core 13 | 14 | source-repository-package 15 | type: git 16 | location: https://github.com/composewell/streamly-process.git 17 | tag: c1ce40ebe84973d0c54dd27fb17e337bf9ca4a46 18 | 19 | source-repository-package 20 | type: git 21 | location: https://github.com/composewell/streamly-shell.git 22 | tag: 070ff21fda7aab8ca45b08b330746299a91eb981 23 | 24 | source-repository-package 25 | type: git 26 | location: https://github.com/composewell/streamly-coreutils.git 27 | tag: 051ef37a5fa399f039d0530e8d642fd79ec6aec2 28 | 29 | source-repository-package 30 | type: git 31 | location: https://github.com/composewell/bench-report.git 32 | tag: a9bbaecb19eda832edf32ae274fef48895facf0b 33 | 34 | package bench-report 35 | flags: +no-charts 36 | -------------------------------------------------------------------------------- /cabal.project.Werror: -------------------------------------------------------------------------------- 1 | packages: . 2 | 3 | package streaming-benchmarks 4 | ghc-options: -Werror 5 | -------------------------------------------------------------------------------- /cabal.project.user: -------------------------------------------------------------------------------- 1 | packages: streaming-benchmarks.cabal 2 | 3 | -- source-repository-package 4 | -- type: git 5 | -- location: https://github.com/composewell/streamly.git 6 | -- tag: master 7 | -- subdir: core 8 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | # CAUTION! a spelling mistake in arg string is ignored silently. 2 | # 3 | # nix-shell --argstr c2nix "--flag examples-sdl" 4 | 5 | # To use ghc-8.6.5 6 | # nix-shell --argstr compiler "ghc865" 7 | 8 | { 9 | nixpkgs ? 10 | import (builtins.fetchTarball 11 | https://github.com/NixOS/nixpkgs/archive/refs/tags/22.05.tar.gz) 12 | {} 13 | , compiler ? "ghc922" 14 | , c2nix ? "" # cabal2nix CLI options 15 | # TODO 16 | #, sources ? [] # e.g. [./. ./benchmark] 17 | #, hdeps ? [] # e.g. [time, mtl] 18 | #, deps ? [] # e.g. [SDL2] 19 | }: 20 | let haskellPackages = 21 | if compiler == "default" 22 | then nixpkgs.haskellPackages 23 | else nixpkgs.haskell.packages.${compiler}; 24 | 25 | # we can possibly avoid adding our package to HaskellPackages like 26 | # in the case of nix-shell for a single package? 27 | mkPackage = super: pkg: path: opts: inShell: 28 | let orig = super.callCabal2nixWithOptions pkg path opts {}; 29 | in if inShell 30 | # Avoid copying the source directory to nix store by using 31 | # src = null. 32 | then orig.overrideAttrs (oldAttrs: { src = null; }) 33 | else orig; 34 | 35 | flags = "--benchmark" + " " + c2nix; 36 | 37 | mkHaskellPackages = inShell: 38 | haskellPackages.override { 39 | # We could disbale doCheck on all like this, but it would make the 40 | # whole world rebuild, we can't use the binary cache 41 | #packageSetConfig = self: super: { 42 | # mkDerivation = drv: super.mkDerivation (drv // { 43 | # doCheck = false; 44 | # }); 45 | #}; 46 | overrides = self: super: 47 | with nixpkgs.haskell.lib; 48 | { 49 | streaming-benchmarks = 50 | mkPackage super "streaming-benchmarks" 51 | ./. flags inShell; 52 | 53 | streamly = 54 | nixpkgs.haskell.lib.overrideCabal 55 | (super.callHackageDirect 56 | { pkg = "streamly"; 57 | ver = "0.8.3"; 58 | sha256 = "sha256-A6/S/yvmOXVabVAR6x4a/XYszzeogSU4vOuyLLSGFvE="; 59 | } {}) 60 | (old: 61 | { librarySystemDepends = 62 | if builtins.currentSystem == "x86_64-darwin" 63 | then [nixpkgs.darwin.apple_sdk.frameworks.Cocoa] 64 | else []; 65 | enableLibraryProfiling = false; 66 | doHaddock = false; 67 | }); 68 | 69 | lockfree-queue = 70 | super.callHackageDirect 71 | { pkg = "lockfree-queue"; 72 | ver = "0.2.4"; 73 | sha256 = "1bj9agy3x0yjbscpjgn96gpnj4lvkh39spjvy3jnrr3a42v3ynw7"; 74 | } {}; 75 | 76 | #fusion-plugin = 77 | # super.callHackageDirect 78 | # { pkg = "fusion-plugin"; 79 | # ver = "0.2.3"; 80 | # sha256 = "073wbhdxj1sh5160blaihbzkkhabs8s71pqhag16lvmgbb7a3hla"; 81 | # } {}; 82 | }; 83 | }; 84 | 85 | hspkgs = mkHaskellPackages true; 86 | 87 | shell = hspkgs.shellFor { 88 | packages = p: 89 | [ p.streaming-benchmarks 90 | ]; 91 | doBenchmark = true; 92 | shellHook = '' 93 | export CABAL_DIR="$(pwd)/.cabal.nix" 94 | if test -n "$PS_SHELL" 95 | then 96 | export PS1="$PS_SHELL\[$bldred\](nix)\[$txtrst\] " 97 | fi 98 | ''; 99 | }; 100 | in if nixpkgs.lib.inNixShell 101 | then shell 102 | else (mkHaskellPackages false).streaming-benchmarks 103 | -------------------------------------------------------------------------------- /docs/benchmarking-notes.md: -------------------------------------------------------------------------------- 1 | # Benchmarking Correctly 2 | 3 | ## Reduce noise overhead 4 | 5 | A common mistake in writing benchmarks is to mask signal with noise. 6 | For example, if we write the benchmarks such as to convert the stream 7 | to a list then the time consumed by list creation itself will mask the 8 | stream processing time. The total time would be: 9 | 10 | ``` 11 | total = n * list cons overhead + n * stream processing overhead 12 | ``` 13 | 14 | If the list cons overhead is too high it will just mask the stream 15 | processing time which is what we are really interested in. We need to 16 | ensure that any such noise factor is either small enough to neglect or 17 | we measure and deduct it from the results. 18 | 19 | ## Describe signal and noise 20 | 21 | Ideally in case of micro benchmarks, with each benchmark we need to 22 | explain what exactly is being measured or what is the benchmarking code 23 | doing. For example, in the previous example we can mention that the 24 | benchmark measures the list creation time plus the stream operation time; 25 | the list creation time dominates. 26 | 27 | Each benchmark should have a description of what it is measuring. 28 | 29 | ## Caveats for fair comparison 30 | 31 | To avoid the domination of list creation time we should choose a 32 | stream exit operation that takes the minimum amount of time. For 33 | example, we could use a sum fold on the output of the stream instead of 34 | converting it into a list. We assume that the fold time would be much 35 | lower. However, this means that we are measuring the fold time + the 36 | stream operation time. If the fold time for a particular library is 37 | high then we are really measuring the fold performance for that rather 38 | than measuring the performance of that particular operation. 39 | 40 | The ideal would be to break down the components involved in a 41 | benchmark and show each component separately. However, in the 42 | real world it is a combination of operations that we are going 43 | to use. That's why micro benchmarks are only useful to optimize 44 | individual operations the overall performance may be dominated by the 45 | most frequent operations and the weakest link in the whole chain. 46 | 47 | ## Measuring maxrss 48 | 49 | Memory once requested from the OS is never released back to the OS by 50 | `GHC` (this has changed since GHC 9.2). This may lead to false `maxrss` 51 | reporting when there are multiple benchmarks in the same benchmark 52 | recipe file. We run each benchmark in an isolated process to avoid 53 | that. The `bench.sh` script takes care of this, running the benchmark 54 | executable directly may not give correct results if all benchmarks are 55 | run together. 56 | 57 | ## Avoiding interference across benchmarks 58 | 59 | Running benchmarks in isolation also ensures avoiding any other kind of 60 | interference (e.g. unwanted sharing) among benchmarks. Though this may 61 | not be able to avoid any compile time interference, e.g. sharing of data 62 | across benchmarks may lead GHC to generate suboptimal code. 63 | 64 | ## Fragile inlining 65 | 66 | We have tried to optimize (`INLINE` etc.) each library's code as much as we 67 | could, library authors are encouraged to take a look at if their library is 68 | being used in a fully optimized manner and report if there are any issues. 69 | -------------------------------------------------------------------------------- /lib/Benchmarks/BenchTH.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TemplateHaskell #-} 2 | 3 | module Benchmarks.BenchTH 4 | ( mkBench 5 | , mkBenchIO 6 | , mkBenchN 7 | , purePackages 8 | , monadicPackages 9 | , monadicArrays 10 | , allPackages 11 | ) where 12 | 13 | import Benchmarks.Common 14 | (benchIO, benchIOAction, benchPure, benchPureFunc, benchIOArray) 15 | import Language.Haskell.TH.Syntax (Q, Exp, mkName) 16 | import Language.Haskell.TH.Lib (varE) 17 | 18 | -- First item in the tuple is the module name and the second one is the 19 | -- corresponding benchmark group name. 20 | -- 21 | monadicPackages :: [(String, String)] 22 | monadicPackages = 23 | [ ("Streamly", "streamly") 24 | , ("VectorStreams", "streams-vector") 25 | , ("Streaming", "streaming") 26 | , ("Machines", "machines") 27 | , ("Pipes", "pipes") 28 | , ("Conduit", "conduit") 29 | , ("Drinkery", "drinkery") 30 | ] 31 | 32 | purePackages :: [(String, String)] 33 | purePackages = 34 | [ -- stream like packages 35 | ("List", "list") 36 | , ("StreamlyPure", "pure-streamly") 37 | , ("DList", "dlist") 38 | , ("Sequence", "sequence") 39 | , ("ByteStringLazy", "lazy-bytestring") 40 | 41 | -- array like packages 42 | , ("ByteString", "bytestring") 43 | , ("Text", "text") 44 | , ("Vector", "vector") 45 | , ("VectorUnboxed", "unboxed-vector") 46 | , ("VectorStorable", "storable-vector") 47 | ] 48 | 49 | monadicArrays :: [(String, String)] 50 | monadicArrays = [("StreamlyArray", "array-streamly")] 51 | 52 | allPackages :: [(String, String)] 53 | allPackages = 54 | purePackages 55 | ++ monadicPackages 56 | ++ monadicArrays 57 | 58 | -- mkBench 59 | -- 60 | mkBench :: String -> String -> String -> String -> Q Exp 61 | mkBench f x mdl bname = 62 | case lookup mdl purePackages of 63 | Nothing -> case lookup mdl monadicPackages of 64 | Just _ -> 65 | [| benchIO bname $(varE (mkName f)) $(varE (mkName x)) |] 66 | Nothing -> 67 | if mdl == "StreamlyArray" 68 | then 69 | [| benchIOArray bname 70 | $(varE (mkName f)) 71 | $(varE (mkName x)) 72 | |] 73 | else error $ 74 | "module " ++ show mdl ++ " not found in module list" 75 | Just _ -> 76 | [| benchPure bname $(varE (mkName f)) $(varE (mkName x)) |] 77 | 78 | mkBenchN :: String -> String -> Int -> String -> String -> Q Exp 79 | mkBenchN f x n mdl bname = 80 | case lookup mdl purePackages of 81 | Nothing -> case lookup mdl monadicPackages of 82 | Just _ -> 83 | [| benchIO bname $(varE (mkName f)) ($(varE (mkName x)) n) |] 84 | Nothing -> 85 | if mdl == "StreamlyArray" 86 | then 87 | [| benchIOArray bname 88 | $(varE (mkName f)) 89 | ($(varE (mkName x)) n) 90 | |] 91 | else 92 | error $ 93 | "module " ++ show mdl ++ " not found in module list" 94 | Just _ -> 95 | [| benchPure bname $(varE (mkName f)) ($(varE (mkName x)) n) 96 | |] 97 | 98 | mkBenchIO :: String -> String -> String -> Q Exp 99 | mkBenchIO action mdl bname = 100 | case lookup mdl purePackages of 101 | Nothing -> case lookup mdl monadicPackages of 102 | Just _ -> 103 | [| benchIOAction bname $(varE (mkName action)) |] 104 | Nothing -> 105 | if mdl == "StreamlyArray" 106 | then 107 | [| benchIOArray "array-streamly" $(varE (mkName action)) |] 108 | else error $ 109 | "module " ++ show mdl ++ " not found in module list" 110 | Just _ -> 111 | [| benchPureFunc bname $(varE (mkName action)) |] 112 | -------------------------------------------------------------------------------- /lib/Benchmarks/BenchmarkTH.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE TemplateHaskell #-} 2 | {-# LANGUAGE DeriveLift #-} 3 | 4 | module Benchmarks.BenchmarkTH 5 | ( createBgroupSink 6 | , createBgroupSinkN 7 | , createBgroupSrc 8 | , createBgroupIO 9 | , pureMods 10 | , monadicMods 11 | , allMods 12 | , benchMods 13 | , iterMods 14 | , Select (..) 15 | ) where 16 | 17 | import Data.List ((\\)) 18 | import Language.Haskell.TH.Syntax (Q, Exp, Lift) 19 | 20 | import Benchmarks.BenchTH 21 | 22 | benchMods, iterMods, pureMods, monadicMods, monadicArrayMods, allMods :: [String] 23 | pureMods = map fst purePackages 24 | monadicMods = map fst monadicPackages 25 | monadicArrayMods = map fst monadicArrays 26 | allMods = pureMods ++ monadicMods ++ monadicArrayMods 27 | benchMods = allMods \\ ["DList"] 28 | iterMods = allMods \\ 29 | [ "DList" 30 | , "Streaming" 31 | , "Machines" 32 | , "Pipes" 33 | , "Conduit" 34 | , "Drinkery" 35 | , "VectorStreams" 36 | ] 37 | 38 | data Select = Exclude [String] | Include [String] deriving Lift 39 | 40 | -- | createBgroupSink 41 | -- 42 | createBgroupSink :: Select -> String -> String -> String -> Q Exp 43 | createBgroupSink select modName bname fname = 44 | case select of 45 | Exclude mods -> f elem mods 46 | Include mods -> f notElem mods 47 | 48 | where 49 | 50 | f p mods = 51 | if p modName mods 52 | then [| Nothing |] 53 | else [| Just $(mkBench "source" fname modName bname) |] 54 | 55 | -- | createBgroupSink 56 | -- 57 | createBgroupSinkN :: Select -> String -> String -> String -> Int -> Q Exp 58 | createBgroupSinkN select modName bname fname n = 59 | case select of 60 | Exclude mods -> f elem mods 61 | Include mods -> f notElem mods 62 | 63 | where 64 | 65 | f p mods = 66 | if p modName mods 67 | then [| Nothing |] 68 | else [| Just $(mkBenchN "source" fname n modName bname) |] 69 | 70 | createBgroupSrc :: Select -> String -> String -> String -> Q Exp 71 | createBgroupSrc select modName bname fname = 72 | case select of 73 | Exclude mods -> f elem mods 74 | Include mods -> f notElem mods 75 | 76 | where 77 | 78 | f p mods = 79 | if p modName mods 80 | then [| Nothing |] 81 | else [| Just $(mkBench fname "toNull" modName bname) |] 82 | 83 | createBgroupIO :: Select -> String -> String -> String -> Q Exp 84 | createBgroupIO select modName bname fname = 85 | case select of 86 | Exclude mods -> f elem mods 87 | Include mods -> f notElem mods 88 | 89 | where 90 | 91 | f p mods = 92 | if p modName mods 93 | then [| Nothing |] 94 | else [| Just $(mkBenchIO fname modName bname) |] 95 | -------------------------------------------------------------------------------- /lib/Benchmarks/Common.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : Benchmarks.Common 3 | -- Copyright : (c) 2018 Harendra Kumar 4 | -- 5 | -- License : MIT 6 | -- Maintainer : harendra.kumar@gmail.com 7 | 8 | module Benchmarks.Common 9 | ( value 10 | , maxValue 11 | , appendValue 12 | , benchIO 13 | , benchIOAction 14 | , benchIOArray 15 | , benchId 16 | , benchPure 17 | , benchPureFunc 18 | ) where 19 | 20 | import Control.DeepSeq (NFData) 21 | import Data.Functor.Identity (Identity, runIdentity) 22 | import System.Random (randomRIO) 23 | 24 | import Gauge 25 | 26 | value, maxValue,appendValue :: Int 27 | value = 1000000 28 | maxValue = value + 1000 29 | appendValue = 10000 30 | 31 | {-# INLINE benchIOAction #-} 32 | benchIOAction :: NFData b => String -> (Int -> IO b) -> Benchmark 33 | benchIOAction name action = bench name $ nfIO $ randomRIO (1,1) >>= action 34 | 35 | {-# INLINE benchIO #-} 36 | benchIO :: (NFData b) => String -> (Int -> a) -> (a -> IO b) -> Benchmark 37 | benchIO name src f = bench name $ nfIO $ randomRIO (1,1) >>= f . src 38 | 39 | {-# INLINE benchId #-} 40 | benchId :: (NFData b) => String -> (Int -> a) -> (a -> Identity b) -> Benchmark 41 | benchId name src f = bench name $ nf (runIdentity . f) (src 10) 42 | 43 | {-# INLINE benchPureFunc #-} 44 | benchPureFunc :: NFData b => String -> (Int -> b) -> Benchmark 45 | benchPureFunc name action = 46 | bench name $ nfIO $ randomRIO (1,1) >>= return . action 47 | 48 | {-# INLINE benchPure #-} 49 | benchPure :: (NFData b) => String -> (Int -> a) -> (a -> b) -> Benchmark 50 | benchPure name src f = 51 | bench name $ nfIO $ randomRIO (1,1) >>= return . f . src 52 | 53 | {-# INLINE benchIOArray #-} 54 | benchIOArray :: NFData b => String -> (Int -> IO a) -> (a -> IO b) -> Benchmark 55 | benchIOArray name src f = 56 | bench name $ nfIO $ randomRIO (1,1) >>= src >>= f 57 | -------------------------------------------------------------------------------- /lib/Benchmarks/DefaultMain.hs: -------------------------------------------------------------------------------- 1 | -- | 2 | -- Module : Main 3 | -- Copyright : (c) 2018 Harendra Kumar 4 | -- 5 | -- License : MIT 6 | -- Maintainer : harendra.kumar@gmail.com 7 | 8 | {-# LANGUAGE TemplateHaskell #-} 9 | 10 | module Benchmarks.DefaultMain (defaultMain) where 11 | 12 | import Data.List ((\\)) 13 | import Data.Maybe (catMaybes) 14 | import Gauge (bgroup) 15 | import Language.Haskell.TH.Syntax (Q, Exp) 16 | 17 | import qualified Gauge as Gauge (defaultMain) 18 | 19 | import Benchmarks.BenchmarkTH 20 | import Prelude hiding (all) 21 | 22 | all, most, almost :: Select 23 | all = Exclude [] 24 | most = Exclude ["DList"] 25 | almost = Exclude ["DList", "Sequence", "ByteStringLazy"] 26 | 27 | defaultMain :: String -> Q Exp 28 | defaultMain name = [| do 29 | Gauge.defaultMain [ bgroup (name ++ "/benchmarks") $ catMaybes 30 | [ Just $ bgroup "elimination" $ catMaybes 31 | [ $(createBgroupSink all name "drain" "toNull") 32 | , $(createBgroupSink (Exclude ["List"]) name "toList" "toList") 33 | , $(createBgroupSink all name "foldl'" "foldl") 34 | , $(createBgroupSink most name "last" "last") 35 | , $(createBgroupSrc (Include ["Streamly", "List", "VectorStreams"]) 36 | name "enumInt" "sourceIntFromThenTo") 37 | ] 38 | , Just $ bgroup "transformation" $ catMaybes 39 | [ $(createBgroupSinkN almost name "scan" "scan" 1) 40 | , $(createBgroupSinkN all name "map" "map" 1) 41 | , $(createBgroupSinkN most name "mapM" "mapM" 1) 42 | ] 43 | , Just $ bgroup "transformationX4" $ catMaybes 44 | [ $(createBgroupSinkN almost name "scan x 4" "scan" 4) 45 | , $(createBgroupSinkN all name "map x 4" "map" 4) 46 | , $(createBgroupSinkN most name "mapM x 4" "mapM" 4) 47 | ] 48 | , Just $ bgroup "filtering" $ catMaybes 49 | [ $(createBgroupSinkN most name "filter-all-out" "filterAllOut" 1) 50 | , $(createBgroupSinkN most name "filter-all-in" "filterAllIn" 1) 51 | , $(createBgroupSinkN most name "drop-all" "dropAll" 1) 52 | , $(createBgroupSinkN most name "takeWhile-true" "takeWhileTrue" 1) 53 | , $(createBgroupSinkN most name "filter-even" "filterEven" 1) 54 | , $(createBgroupSinkN most name "take-all" "takeAll" 1) 55 | , $(createBgroupSinkN most name "drop-one" "dropOne" 1) 56 | , $(createBgroupSinkN most name "dropWhile-true" "dropWhileTrue" 1) 57 | , $(createBgroupSinkN most name "dropWhile-false" "dropWhileFalse" 1) 58 | ] 59 | -- drop-all/dropWhile-true/filter-all-out x 4 would be the same as 60 | -- drop-all/dropWhile-true/filter-all-out x 1 as they would already 61 | -- drop all elements and do nothing in the next iterations 62 | , Just $ bgroup "filteringX4" $ catMaybes 63 | [ $(createBgroupSinkN most name "filter-even x 4" "filterEven" 4) 64 | -- , $(createBgroupSinkN most name "filter-all-out x 4" "filterAllOut" 4) 65 | , $(createBgroupSinkN most name "filter-all-in x 4" "filterAllIn" 4) 66 | , $(createBgroupSinkN most name "take-all x 4" "takeAll" 4) 67 | , $(createBgroupSinkN most name "takeWhile-true x 4" "takeWhileTrue" 4) 68 | , $(createBgroupSinkN most name "drop-one x 4" "dropOne" 4) 69 | -- , $(createBgroupSinkN most name "drop-all x 4" "dropAll" 4) 70 | -- , $(createBgroupSinkN most name "dropWhile-true x 4" "dropWhileTrue" 4) 71 | , $(createBgroupSinkN most name "dropWhile-false x 4" "dropWhileFalse" 4) 72 | ] 73 | , Just $ bgroup "mixedX4" $ catMaybes 74 | [ $(createBgroupSinkN almost name "scan-map x 4" "scanMap" 4) 75 | , $(createBgroupSinkN almost name "drop-scan x 4" "dropScan" 4) 76 | , $(createBgroupSinkN almost name "take-scan x 4" "takeScan" 4) 77 | , $(createBgroupSinkN almost name "filter-scan x 4" "filterScan" 4) 78 | , $(createBgroupSinkN most name "drop-map x 4" "dropMap" 4) 79 | , $(createBgroupSinkN most name "take-drop x 4" "takeDrop" 4) 80 | , $(createBgroupSinkN most name "take-map x 4" "takeMap" 4) 81 | , $(createBgroupSinkN most name "filter-drop x 4" "filterDrop" 4) 82 | , $(createBgroupSinkN most name "filter-take x 4" "filterTake" 4) 83 | , $(createBgroupSinkN most name "filter-map x 4" "filterMap" 4) 84 | ] 85 | , $(createBgroupSink (Exclude ["DList", "StreamlyArray"]) name "zip" "zip") 86 | -- XXX use 4x250k concatMap for a comparative idea of cost wrt other ops 87 | , $(createBgroupSink (Include 88 | [ "List" 89 | , "Streamly" 90 | , "StreamlyPure" 91 | , "VectorStreams" 92 | , "Vector" 93 | , "VectorStorable" 94 | , "VectorUnboxed" 95 | , "ByteString" 96 | , "ByteStringLazy" 97 | , "Text" 98 | ]) name "concatMap" "concatMap") 99 | , $(createBgroupSink (Exclude 100 | [ "List" 101 | , "DList" 102 | , "Streamly" 103 | , "StreamlyPure" 104 | , "VectorStreams" 105 | , "Vector" 106 | , "VectorStorable" 107 | , "VectorUnboxed" 108 | , "ByteString" 109 | , "ByteStringLazy" 110 | , "Sequence" 111 | , "StreamlyArray" 112 | , "Text" 113 | ]) name "concatMapFoldable" "concatMapFoldable") 114 | 115 | -- XXX The rest are StreamK operations, use a separate bench group/suite 116 | -- for these? 117 | , $(createBgroupIO (Exclude 118 | [ "Drinkery" 119 | , "StreamlyArray" 120 | ]) name "appendR (1/100)" "appendSourceR") 121 | , $(createBgroupIO (Include 122 | [ "DList" 123 | , "Conduit" 124 | -- , "Streamly" 125 | -- , "StreamlyPure" 126 | ]) name "appendL (1/100)" "appendSourceL") 127 | -- Perform 100,000 mapM recursively over a stream of length 10 128 | , Just $ bgroup "iterated" $ catMaybes 129 | [ $(createBgroupSrc (Include (iterMods \\ pureMods)) name 130 | "mapM" "iterateMapM") 131 | , $(createBgroupSrc (Include (iterMods \\ ["Sequence", "ByteStringLazy"])) name 132 | "scan" "iterateScan") 133 | , $(createBgroupSrc (Include iterMods) name 134 | "filterEven" "iterateFilterEven") 135 | , $(createBgroupSrc (Include iterMods) name 136 | "takeAll" "iterateTakeAll") 137 | , $(createBgroupSrc (Include iterMods) name 138 | "dropOne" "iterateDropOne") 139 | , $(createBgroupSrc (Include iterMods) name 140 | "dropWhileFalse" "iterateDropWhileFalse") 141 | , $(createBgroupSrc (Include iterMods) name 142 | "dropWhileTrue" "iterateDropWhileTrue") 143 | ] 144 | ] 145 | ] 146 | |] 147 | -------------------------------------------------------------------------------- /stack.yaml: -------------------------------------------------------------------------------- 1 | resolver: lts-21.25 2 | packages: 3 | - '.' 4 | #extra-deps: 5 | #- streamly-0.8.3 6 | 7 | rebuild-ghc-options: true 8 | -------------------------------------------------------------------------------- /streaming-benchmarks.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 2.2 2 | name: streaming-benchmarks 3 | version: 0.4.0 4 | license: MIT 5 | license-file: LICENSE 6 | author: Composewell Technologies 7 | maintainer: streamly@composewell.com 8 | stability: provisional 9 | homepage: https://streamly.composewell.com 10 | bug-reports: http://github.com/composewell/streaming-benchmarks/issues 11 | copyright: Copyright (c) 2017 Harendra Kumar 12 | category: Streamly, Streaming, Benchmark 13 | synopsis: Measures and compares the performance of streaming libraries 14 | description: 15 | This package provides micro-benchmarks to measure and compare the 16 | performance of various streaming implementations in Haskell. 17 | . 18 | The following packages are supported: 19 | . 20 | * base (Haskell lists) 21 | * streamly 22 | * streaming 23 | * pipes 24 | * machines 25 | * conduit 26 | * drinkery 27 | 28 | tested-with: GHC==8.8.4, GHC==8.10.4 29 | build-type: Simple 30 | extra-source-files: 31 | Benchmarks/VectorCommon.hs 32 | Changelog.md 33 | README.md 34 | bench-runner/Main.hs 35 | bench-runner/bench-runner.cabal 36 | bench-runner/cabal.project.user 37 | 38 | extra-doc-files: 39 | docs/benchmarking-notes.md 40 | 41 | source-repository head 42 | type: git 43 | location: https://github.com/composewell/streaming-benchmarks 44 | 45 | flag no-fusion-plugin 46 | description: Disable fusion plugin for streamly benchmarks 47 | manual: True 48 | default: False 49 | 50 | flag use-gauge 51 | description: Use gauge instead of tasty-bench for benchmarking 52 | manual: True 53 | default: False 54 | 55 | flag drinkery 56 | description: Use drinkery 57 | manual: True 58 | default: False 59 | 60 | flag conduit 61 | description: Use conduit 62 | manual: True 63 | default: False 64 | 65 | flag pipes 66 | description: Use pipes 67 | manual: True 68 | default: False 69 | 70 | flag machines 71 | description: Use machines 72 | manual: True 73 | default: False 74 | 75 | flag streaming 76 | description: Use streaming 77 | manual: True 78 | default: False 79 | 80 | common lib-options 81 | default-language: Haskell2010 82 | ghc-options: 83 | -Wall 84 | -Wcompat 85 | -Wunrecognised-warning-flags 86 | -Widentities 87 | -Wincomplete-record-updates 88 | -Wincomplete-uni-patterns 89 | -Wredundant-constraints 90 | -Wnoncanonical-monad-instances 91 | -Rghc-timing 92 | +RTS -M512M -RTS 93 | -O2 94 | build-depends: 95 | base == 4.*, 96 | deepseq >= 1.4.4 && < 1.6, 97 | mtl >= 2.2.2 && < 2.4, 98 | random >= 1.0 && < 2.0, 99 | transformers >= 0.5.5 && < 0.7, 100 | template-haskell >= 2.14 && < 2.23 101 | if flag(use-gauge) 102 | build-depends: gauge >= 0.2.4 && < 0.3 103 | else 104 | build-depends: tasty-bench >= 0.3 && < 0.5 105 | , tasty >= 1.4.1 && < 1.6 106 | mixins: tasty-bench 107 | (Test.Tasty.Bench as Gauge 108 | , Test.Tasty.Bench as Gauge.Main 109 | ) 110 | 111 | common bench-options 112 | import: lib-options 113 | ghc-options: 114 | -rtsopts 115 | -with-rtsopts "-T" 116 | build-depends: streaming-benchmarks 117 | 118 | common streamly-options 119 | import: bench-options 120 | ghc-options: 121 | -fdicts-strict 122 | -fspec-constr-recursive=16 123 | -fmax-worker-args=16 124 | if !flag(no-fusion-plugin) 125 | ghc-options: -fplugin Fusion.Plugin 126 | build-depends: 127 | streamly-core >= 0.1.0 && < 0.3.1 128 | if !flag(no-fusion-plugin) 129 | build-depends: fusion-plugin >= 0.2 && < 0.3 130 | 131 | library 132 | import: lib-options 133 | hs-source-dirs: lib 134 | exposed-modules: 135 | Benchmarks.Common 136 | , Benchmarks.BenchTH 137 | , Benchmarks.BenchmarkTH 138 | , Benchmarks.DefaultMain 139 | 140 | -- Streams 141 | benchmark Streamly 142 | import: streamly-options 143 | type: exitcode-stdio-1.0 144 | main-is: Benchmarks/Streamly.hs 145 | ghc-options: -main-is Benchmarks.Streamly 146 | 147 | benchmark StreamlyPure 148 | import: streamly-options 149 | type: exitcode-stdio-1.0 150 | main-is: Benchmarks/StreamlyPure.hs 151 | ghc-options: -main-is Benchmarks.StreamlyPure 152 | 153 | benchmark List 154 | import: bench-options 155 | type: exitcode-stdio-1.0 156 | main-is: Benchmarks/List.hs 157 | ghc-options: -main-is Benchmarks.List 158 | 159 | benchmark DList 160 | import: bench-options 161 | type: exitcode-stdio-1.0 162 | main-is: Benchmarks/DList.hs 163 | ghc-options: -main-is Benchmarks.DList 164 | build-depends: dlist >= 0.7 && < 1.1 165 | 166 | benchmark Streaming 167 | import: bench-options 168 | type: exitcode-stdio-1.0 169 | main-is: Benchmarks/Streaming.hs 170 | ghc-options: -main-is Benchmarks.Streaming 171 | if flag(streaming) 172 | build-depends: streaming >= 0.1.4 && < 0.3 173 | else 174 | buildable: False 175 | 176 | benchmark Machines 177 | import: bench-options 178 | type: exitcode-stdio-1.0 179 | main-is: Benchmarks/Machines.hs 180 | ghc-options: -main-is Benchmarks.Machines 181 | if flag(machines) 182 | build-depends: machines >= 0.6.0 && < 0.8 183 | else 184 | buildable: False 185 | 186 | benchmark Pipes 187 | import: bench-options 188 | type: exitcode-stdio-1.0 189 | main-is: Benchmarks/Pipes.hs 190 | ghc-options: -main-is Benchmarks.Pipes 191 | if flag(pipes) 192 | build-depends: pipes >= 4 && < 4.4 193 | else 194 | buildable: False 195 | 196 | benchmark Conduit 197 | import: bench-options 198 | type: exitcode-stdio-1.0 199 | main-is: Benchmarks/Conduit.hs 200 | ghc-options: -main-is Benchmarks.Conduit 201 | if flag(conduit) 202 | build-depends: conduit >= 1.3 && < 1.4 203 | else 204 | buildable: False 205 | 206 | benchmark Drinkery 207 | import: bench-options 208 | type: exitcode-stdio-1.0 209 | main-is: Benchmarks/Drinkery.hs 210 | ghc-options: -main-is Benchmarks.Drinkery 211 | if flag(drinkery) 212 | build-depends: drinkery >= 0.3 && < 0.5 213 | else 214 | buildable: False 215 | 216 | -- benchmark SimpleConduit 217 | -- import: bench-options 218 | -- type: exitcode-stdio-1.0 219 | -- main-is: Benchmarks/SimpleConduit.hs 220 | -- ghc-options: -main-is Benchmarks.SimpleConduit 221 | -- build-depends: simple-conduit >= 0.6 && < 0.7 222 | 223 | -- Arrays 224 | benchmark StreamlyArray 225 | import: streamly-options 226 | type: exitcode-stdio-1.0 227 | main-is: Benchmarks/StreamlyArray.hs 228 | ghc-options: 229 | -main-is Benchmarks.StreamlyArray 230 | +RTS -M1500M -RTS 231 | 232 | benchmark Sequence 233 | import: bench-options 234 | type: exitcode-stdio-1.0 235 | main-is: Benchmarks/Sequence.hs 236 | ghc-options: -main-is Benchmarks.Sequence 237 | build-depends: containers >= 0.5 && < 0.8 238 | 239 | benchmark ByteString 240 | import: bench-options 241 | type: exitcode-stdio-1.0 242 | main-is: Benchmarks/ByteString.hs 243 | ghc-options: -main-is Benchmarks.ByteString 244 | build-depends: bytestring >= 0.9 && < 0.13 245 | 246 | benchmark ByteStringLazy 247 | import: bench-options 248 | type: exitcode-stdio-1.0 249 | main-is: Benchmarks/ByteStringLazy.hs 250 | ghc-options: -main-is Benchmarks.ByteStringLazy 251 | build-depends: bytestring >= 0.9 && < 0.13 252 | 253 | benchmark Text 254 | import: bench-options 255 | type: exitcode-stdio-1.0 256 | main-is: Benchmarks/Text.hs 257 | ghc-options: -main-is Benchmarks.Text 258 | build-depends: text >= 1.0 && < 3.0 259 | 260 | benchmark VectorStreams 261 | import: bench-options 262 | type: exitcode-stdio-1.0 263 | main-is: Benchmarks/VectorStreams.hs 264 | ghc-options: -main-is Benchmarks.VectorStreams 265 | build-depends: vector >= 0.12 && < 0.14 266 | 267 | benchmark Vector 268 | import: bench-options 269 | type: exitcode-stdio-1.0 270 | main-is: Benchmarks/Vector.hs 271 | ghc-options: -main-is Benchmarks.Vector 272 | build-depends: vector >= 0.12 && < 0.14 273 | 274 | benchmark VectorUnboxed 275 | import: bench-options 276 | type: exitcode-stdio-1.0 277 | main-is: Benchmarks/VectorUnboxed.hs 278 | ghc-options: -main-is Benchmarks.VectorUnboxed 279 | build-depends: vector >= 0.12 && < 0.14 280 | 281 | benchmark VectorStorable 282 | import: bench-options 283 | type: exitcode-stdio-1.0 284 | main-is: Benchmarks/VectorStorable.hs 285 | ghc-options: -main-is Benchmarks.VectorStorable 286 | build-depends: vector >= 0.12 && < 0.14 287 | --------------------------------------------------------------------------------