@@ -1,9 +1,17 @@
11# Compilation Options
22
3−## Recommended Options
3+## Absolute Minimum
4+
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:
49
5−Benchmarks show that GHC 8.8 has significantly better performance than GHC 8.6
6−in many cases.
10+```
11+main = S.drain $ S.concatMap S.fromList $ S.repeat []
12+```
13+
14+## Recommended Options
715
816Use the following GHC options:
917
@@ -22,33 +30,20 @@ See [Explanation](#explanation) for details about these flags.
2230
2331## Using Fusion Plugin
2432
25−In many cases `fusion-plugin` can improve performance by better stream
26−fusion. However, in some cases performance may also regress. Please note
27−that the `fusion-plugin` package works only for GHC >= 8.6.
33+In many cases
34+[fusion-plugin](https://hackage.haskell.org/package/fusion-plugin) can
35+improve performance by better stream fusion. However, in some cases
36+performance may also regress. Please note that the `fusion-plugin`
37+package works only for GHC >= 8.6.
2838
29−* Install the
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
32−cabal file.
33−
34−* Use the following GHC option in addition to the options listed in the
35− previous section:
39+* Add `fusion-plugin` to the `build-depends` section of your program in
40+ the cabal file.
41+* Add the following to GHC options:
3642
3743```
3844 -fplugin=Fusion.Plugin
3945```
4046
41−## Minimal
42−
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:
47−
48−```
49−main = S.drain $ S.concatMap S.fromList $ S.repeat []
50−```
51−
5247## Explanation
5348
5449* `-fdicts-strict` is needed to avoid [a GHC
@@ -79,7 +74,8 @@ multi-core parallelism use the following GHC options:
7974
8075# Compiler Versions
8176
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.
8379
8480GHC 8.2.2 may hog memory and hang when building certain application using
8581streamly (particularly the benchmark programs in the streamly package).
@@ -87,13 +83,97 @@ Therefore we recommend avoiding using the GHC version 8.2.x.
8783
8884# Performance Optimizations
8985
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
91+programming.
92+
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.
98+
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.
107+
108+Remember, you need to worry about performance only where it matters, try
109+to optimize the fast path and not everything blindly.
110+
111+## INLINE annotations
112+
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.
116+
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.
119+
120+### Early INLINE
121+
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.
128+
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.
136+
137+### Delayed INLINE
138+
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.
144+
145+## Strictness annotations
91146
92−* Look for inlining functions in the fast path
93147* Strictness annotations on data, specially the data used as accumulator in
94148 folds and scans, can help in improving performance.
95149* Strictness annotations on function arguments can help the compiler unbox
96150 constructors in certain cases, improving performance.
97151* Sometimes using `-XStrict` extension can help improve performance, if so you
98− may be missing some strictness annotations.
99−* Use tail recursion for streaming data or for large loops
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.
154+
155+## Use tail recursion
156+
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.
161+
162+# Build times and space considerations
163+
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.
169+
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.
175+
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.