5At the very least `-O -fdicts-strict` compilation options are
6required. If these options are not used, the program may exhibit memory
7hog. For example, the following program, if compiled without an
8optimization option, is known to hog memory:
11main = S.drain $ S.concatMap S.fromList $ S.repeat []
16Use the following GHC options:
22 -fspec-constr-recursive=16
25Important Note: In certain cases it is possible that GHC takes too long to
26compile with `-fspec-constr-recursive=16`, if that happens please reduce the
27value or remove that option.
29See [Explanation](#explanation) for details about these flags.
34[fusion-plugin](https://hackage.haskell.org/package/fusion-plugin) can
35improve performance by better stream fusion. However, in some cases
36performance may also regress. Please note that the `fusion-plugin`
37package works only for GHC >= 8.6.
39* Add `fusion-plugin` to the `build-depends` section of your program in
41* Add the following to GHC options:
44 -fplugin=Fusion.Plugin
49* `-fdicts-strict` is needed to avoid [a GHC
50issue](https://gitlab.haskell.org/ghc/ghc/issues/17745) leading to
51memory leak in some cases.
53* `-fspec-constr-recursive` is needed for better stream fusion by enabling
54the `SpecConstr` optimization in more cases. Large values used with this flag
55may lead to huge compilation times and code bloat, if that happens please avoid
56it or use a lower value (e.g. 3 or 4).
58* `-fmax-worker-args` is needed for better stream fusion by enabling the
59`SpecConstr` optimization in some important cases.
61* `-fplugin=Fusion.Plugin` enables predictable stream fusion
62optimization in certain cases by helping the compiler inline internal
63bindings and therefore enabling case-of-case optimization. In some
64cases, especially in some file IO benchmarks, it can make a difference of
655-10x better performance.
67# Multi-core Parallelism
69Concurrency without a threaded runtime may be a bit more efficient. Do not use
70threaded runtime unless you really need multi-core parallelism. To get
71multi-core parallelism use the following GHC options:
73 `-threaded -with-rtsopts "-N"`
77Use GHC 8.8 for best performance. Benchmarks show that GHC 8.8 has
78significantly better performance than GHC 8.6 in many cases.
80GHC 8.2.2 may hog memory and hang when building certain application using
81streamly (particularly the benchmark programs in the streamly package).
82Therefore we recommend avoiding using the GHC version 8.2.x.
84# Performance Optimizations
86A "closed loop" is any streamly code that generates a stream using
87unfold (or conceptually any stream generation combinator) and ends
88up eliminating it with a fold (conceptually any stream elimination
89combinator). It is essentially a loop processing multiple elements in
90a stream sequence, just like a `for` or `while` loop in imperative
93Closed loops are generated in a modular fashion by stream generation,
94transformation and elimination combinators in streamly. Combinators
95transfer data to the next stage or combinator using data constructors.
96These data constructors are eliminated by the compiler using `stream
97fusion` optimizations, generating a very efficient loop.
99However, stream fusion optimization depends on proper inlining of the
100combinators involved. The fusion-plugin package mentioned earlier
101fills gaps for several optimizations that GHC does not perform
102automatically. It automatically inlines the internal definitions
103that involve the constructors we want to eliminate. In some cases
104fusion-plugin may not help and programmer may have to annotate the code
105manually for complete fusion. In this section we mention some of the
106cases where programmer annotation may help in stream fusion.
108Remember, you need to worry about performance only where it matters, try
109to optimize the fast path and not everything blindly.
113It may help to add INLINE annotations on any intermediate functions
114involved in a closed loop. In some cases you may have to add an inline
115phase as well as described below.
117Usually GHC has three inline phases - the first phase is pahse-2, the
118second phase is phase-1 and the last one is phase-0.
122Generally, you only have to inline the combinators or functions
123participating in a loop and not the whole loop itself. But sometimes
124you may want to inline the whole loop itself inside a larger
125function. In most cases you can just add an INLINE pragma on
126the function containing the loop. But you may need some special
127considerations in some (not common) cases.
129In some cases you may have to use INLINE[2] instead of INLINE which
130means inline the function early in phase-2. This may sometimes be
131needed on the because the performance of several combinators in streamly
132depends on getting inlined in phase-2 and if you use a plain `INLINE`
133annotation GHC may decide to delay the inlining in some cases. This is
134not very common but may be needed sometimes. Perhaps GHC can be fixed or
135we can resolve this using fusion-plugin in future.
139When a function is passed to a higher order function e.g. a function
140passed to `concatMap` or `concatUnfold` then we want the function to be
141inlined after the higher order is inlined so that proper fusion of the
142higher order function can occur. For such cases we usually add INLINE[1]
143on the function being passed to instruct GHC not to inline it too early.
145## Strictness annotations
147* Strictness annotations on data, specially the data used as accumulator in
148 folds and scans, can help in improving performance.
149* Strictness annotations on function arguments can help the compiler unbox
150 constructors in certain cases, improving performance.
151* Sometimes using `-XStrict` extension can help improve performance, if so you
152 may be missing some strictness annotations. `-XStrict` can be used as an aid
153 to detect missing annotations, using it blindly may regress performance.
157Do not use a strict `foldr` or lazy `foldl` unless you know what you are
158doing. Use lazy `foldr` for lazily transforming the stream and strict
159`foldl` for reducing the stream. If you are manually writing recursive
160code, try to use tail recursion where possible.
162# Build times and space considerations
164Haskell, being a pure functional language, confers special powers on
165GHC. It allows GHC to do whole program optimization. In a closed loop
166all the components of the loop are inlined and GHC fuses them together,
167performs many optimizing transformations and churns out an optimized
168fused loop code. Let's call it whole-loop-optimization.
170To be able to fuse the loop by whole-loop-optimization all the parts of the
171loop must be operated on by GHC at the same time to fuse them together. The
172amount of time and memory required to do so depends on the size of the loop.
173Huge loops can take a lot of time and memory. We have seen GHC take 4-5 GB of
174memory when a lot of combinators are used in a single module.
176If a module takes too much time and space we can break it into multiple
177modules moving some non-inlined parts in another module. There is
178another advantage of breaking large modules, it can take advantage of
179parallel compilation if they do not depend on each other.
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list