@@ -1,9 +1,17 @@
1# Compilation Options
1# Compilation Options
3−## Recommended Options
3+## Absolute Minimum
5+At the very least `-O -fdicts-strict` compilation options are
6+required. If these options are not used, the program may exhibit memory
7+hog. For example, the following program, if compiled without an
8+optimization option, is known to hog memory:
5−Benchmarks show that GHC 8.8 has significantly better performance than GHC 8.6
10+```
6−in many cases.
11+main = S.drain $ S.concatMap S.fromList $ S.repeat []
14+## Recommended Options
8Use the following GHC options:
16Use the following GHC options:
@@ -22,33 +30,20 @@ See [Explanation](#explanation) for details about these flags.
23## Using Fusion Plugin
31## Using Fusion Plugin
34+[fusion-plugin](https://hackage.haskell.org/package/fusion-plugin) can
25−In many cases `fusion-plugin` can improve performance by better stream
35+improve performance by better stream fusion. However, in some cases
26−fusion. However, in some cases performance may also regress. Please note
36+performance may also regress. Please note that the `fusion-plugin`
27−that the `fusion-plugin` package works only for GHC >= 8.6.
37+package works only for GHC >= 8.6.
30−[fusion-plugin](https://hackage.haskell.org/package/fusion-plugin)
31−package or add it to the `build-depends` section of your program in the
39+* Add `fusion-plugin` to the `build-depends` section of your program in
32−cabal file.
40+ the cabal file.
34−* Use the following GHC option in addition to the options listed in the
41+* Add the following to GHC options:
38 -fplugin=Fusion.Plugin
44 -fplugin=Fusion.Plugin
43−At the very least `-O -fdicts-strict` compilation options are
44−required. If these options are not used, the program may exhibit memory
45−hog. For example, the following program, if compiled without an
46−optimization option, is known to hog memory:
49−main = S.drain $ S.concatMap S.fromList $ S.repeat []
52## Explanation
47## Explanation
54* `-fdicts-strict` is needed to avoid [a GHC
49* `-fdicts-strict` is needed to avoid [a GHC
@@ -79,7 +74,8 @@ multi-core parallelism use the following GHC options:
80# Compiler Versions
75# Compiler Versions
82−Use GHC 8.8 for best performance.
77+Use GHC 8.8 for best performance. Benchmarks show that GHC 8.8 has
78+significantly better performance than GHC 8.6 in many cases.
84GHC 8.2.2 may hog memory and hang when building certain application using
80GHC 8.2.2 may hog memory and hang when building certain application using
85streamly (particularly the benchmark programs in the streamly package).
81streamly (particularly the benchmark programs in the streamly package).
@@ -87,13 +83,97 @@ Therefore we recommend avoiding using the GHC version 8.2.x.
88# Performance Optimizations
84# Performance Optimizations
90−If performance is below expectations:
86+A "closed loop" is any streamly code that generates a stream using
87+unfold (or conceptually any stream generation combinator) and ends
88+up eliminating it with a fold (conceptually any stream elimination
89+combinator). It is essentially a loop processing multiple elements in
90+a stream sequence, just like a `for` or `while` loop in imperative
93+Closed loops are generated in a modular fashion by stream generation,
94+transformation and elimination combinators in streamly. Combinators
95+transfer data to the next stage or combinator using data constructors.
96+These data constructors are eliminated by the compiler using `stream
97+fusion` optimizations, generating a very efficient loop.
99+However, stream fusion optimization depends on proper inlining of the
100+combinators involved. The fusion-plugin package mentioned earlier
101+fills gaps for several optimizations that GHC does not perform
102+automatically. It automatically inlines the internal definitions
103+that involve the constructors we want to eliminate. In some cases
104+fusion-plugin may not help and programmer may have to annotate the code
105+manually for complete fusion. In this section we mention some of the
106+cases where programmer annotation may help in stream fusion.
108+Remember, you need to worry about performance only where it matters, try
109+to optimize the fast path and not everything blindly.
111+## INLINE annotations
113+It may help to add INLINE annotations on any intermediate functions
114+involved in a closed loop. In some cases you may have to add an inline
115+phase as well as described below.
117+Usually GHC has three inline phases - the first phase is pahse-2, the
118+second phase is phase-1 and the last one is phase-0.
122+Generally, you only have to inline the combinators or functions
123+participating in a loop and not the whole loop itself. But sometimes
124+you may want to inline the whole loop itself inside a larger
125+function. In most cases you can just add an INLINE pragma on
126+the function containing the loop. But you may need some special
127+considerations in some (not common) cases.
129+In some cases you may have to use INLINE[2] instead of INLINE which
130+means inline the function early in phase-2. This may sometimes be
131+needed on the because the performance of several combinators in streamly
132+depends on getting inlined in phase-2 and if you use a plain `INLINE`
133+annotation GHC may decide to delay the inlining in some cases. This is
134+not very common but may be needed sometimes. Perhaps GHC can be fixed or
135+we can resolve this using fusion-plugin in future.
139+When a function is passed to a higher order function e.g. a function
140+passed to `concatMap` or `concatUnfold` then we want the function to be
141+inlined after the higher order is inlined so that proper fusion of the
142+higher order function can occur. For such cases we usually add INLINE[1]
143+on the function being passed to instruct GHC not to inline it too early.
145+## Strictness annotations
92−* Look for inlining functions in the fast path
93* Strictness annotations on data, specially the data used as accumulator in
147* Strictness annotations on data, specially the data used as accumulator in
94 folds and scans, can help in improving performance.
148 folds and scans, can help in improving performance.
95* Strictness annotations on function arguments can help the compiler unbox
149* Strictness annotations on function arguments can help the compiler unbox
96 constructors in certain cases, improving performance.
150 constructors in certain cases, improving performance.
97* Sometimes using `-XStrict` extension can help improve performance, if so you
151* Sometimes using `-XStrict` extension can help improve performance, if so you
98− may be missing some strictness annotations.
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.
99−* Use tail recursion for streaming data or for large loops
155+## Use tail recursion
157+Do not use a strict `foldr` or lazy `foldl` unless you know what you are
158+doing. Use lazy `foldr` for lazily transforming the stream and strict
159+`foldl` for reducing the stream. If you are manually writing recursive
160+code, try to use tail recursion where possible.
162+# Build times and space considerations
164+Haskell, being a pure functional language, confers special powers on
165+GHC. It allows GHC to do whole program optimization. In a closed loop
166+all the components of the loop are inlined and GHC fuses them together,
167+performs many optimizing transformations and churns out an optimized
168+fused loop code. Let's call it whole-loop-optimization.
170+To be able to fuse the loop by whole-loop-optimization all the parts of the
171+loop must be operated on by GHC at the same time to fuse them together. The
172+amount of time and memory required to do so depends on the size of the loop.
173+Huge loops can take a lot of time and memory. We have seen GHC take 4-5 GB of
174+memory when a lot of combinators are used in a single module.
176+If a module takes too much time and space we can break it into multiple
177+modules moving some non-inlined parts in another module. There is
178+another advantage of breaking large modules, it can take advantage of
179+parallel compilation if they do not depend on each other.