on 85b69a7 help sign in
streamly/docs/Build.md
205 lines 8.2 kB history raw
1# GHC Haskell
2## Compiler Versions
4GHC versions 8.0 onwards are supported by streamly per se. However, note
5that some packages we depend on (e.g. `network`) support only last three
6versions of GHC.
8Use GHC 8.8 for best performance. Benchmarks show that GHC 8.8 has
9significantly better performance than GHC 8.6 in many cases.
11GHC 8.2.2 may hog memory and hang when building certain applications using
12streamly (particularly the benchmark programs in the streamly package).
13Therefore, we recommend avoiding using the GHC version 8.2.x.
15## Compilation Options
17### Absolute Minimum
19At the very least `-O -fdicts-strict` compilation options are
20required. If these options are not used, the program may exhibit memory
21hog. For example, the following program, if compiled without an
22optimization option, is known to hog memory:
24```
25main = S.drain $ S.concatMap S.fromList $ S.repeat []
26```
28### Recommended Options
30Use the following GHC options:
32```
33 -O2
34 -fdicts-strict
35 -fmax-worker-args=16
36 -fspec-constr-recursive=16
37```
39Important Note: In certain cases it is possible that GHC takes too long to
40compile with `-fspec-constr-recursive=16`, if that happens please reduce the
41value or remove that option.
43See [Explanation](#explanation) for details about these flags.
45### Using Fusion Plugin
47In many cases
48[fusion-plugin](https://hackage.haskell.org/package/fusion-plugin) can
49improve performance by better stream fusion. However, in some cases
50performance may also regress. Please note that the `fusion-plugin`
51package works only for GHC >= 8.6.
53* Add `fusion-plugin` to the `build-depends` section of your program in
54 the cabal file.
55* Add the following to GHC options:
57```
58 -fplugin=Fusion.Plugin
59```
61### Explanation
63* `-fdicts-strict` is needed to avoid [a GHC
64issue](https://gitlab.haskell.org/ghc/ghc/issues/17745) leading to
65memory leak in some cases.
67* `-fspec-constr-recursive` is needed for better stream fusion by enabling
68the `SpecConstr` optimization in more cases. Large values used with this flag
69may lead to huge compilation times and code bloat, if that happens please avoid
70it or use a lower value (e.g. 3 or 4).
72* `-fmax-worker-args` is needed for better stream fusion by enabling the
73`SpecConstr` optimization in some important cases.
75* `-fplugin=Fusion.Plugin` enables predictable stream fusion
76optimization in certain cases by helping the compiler inline internal
77bindings and therefore enabling case-of-case optimization. In some
78cases, especially in some file IO benchmarks, it can make a difference of
795-10x better performance.
81## Multi-core Parallelism
83Concurrency without a threaded runtime may be a bit more efficient. Do not use
84threaded runtime unless you really need multi-core parallelism. To get
85multi-core parallelism use the following GHC options:
87 `-threaded -with-rtsopts "-N"`
89# Platform Specific
91Streamly supports Linux, macOS and Windows operating systems. Some
92modules and functionality may depend on specific OS kernel features.
93Features/modules may get disabled if the kernel/OS does not support it.
95## Linux
97* File system events notification module is supported only for kernel versions
98 2.6.36 onwards.
100## Mac OSX
102* File system events notification module requires macOS 10.7+ with
103 Xcode/macOS SDK installed (depends on `Cocoa` framework). However, we only
104 test on latest three versions of the OS.
106## Windows
108* File system event notification module is not yet available for Windows.
110# Performance Optimizations
112A "closed loop" is any streamly code that generates a stream using
113unfold (or conceptually any stream generation combinator) and ends
114up eliminating it with a fold (conceptually any stream elimination
115combinator). It is essentially a loop processing multiple elements in
116a stream sequence, just like a `for` or `while` loop in imperative
117programming.
119Closed loops are generated in a modular fashion by stream generation,
120transformation and elimination combinators in streamly. Combinators
121transfer data to the next stage or combinator using data constructors.
122These data constructors are eliminated by the compiler using `stream
123fusion` optimizations, generating a very efficient loop.
125However, stream fusion optimization depends on proper inlining of the
126combinators involved. The fusion-plugin package mentioned earlier
127fills gaps for several optimizations that GHC does not perform
128automatically. It automatically inlines the internal definitions
129that involve the constructors we want to eliminate. In some cases
130fusion-plugin may not help and programmer may have to annotate the code
131manually for complete fusion. In this section we mention some of the
132cases where programmer annotation may help in stream fusion.
134Remember, you need to worry about performance only where it matters, try
135to optimize the fast path and not everything blindly.
137## INLINE annotations
139It may help to add INLINE annotations on any intermediate functions
140involved in a closed loop. In some cases you may have to add an inline
141phase as well as described below.
143Usually GHC has three inline phases - the first phase is pahse-2, the
144second phase is phase-1 and the last one is phase-0.
146### Early INLINE
148Generally, you only have to inline the combinators or functions
149participating in a loop and not the whole loop itself. But sometimes
150you may want to inline the whole loop itself inside a larger
151function. In most cases you can just add an INLINE pragma on
152the function containing the loop. But you may need some special
153considerations in some (not common) cases.
155In some cases you may have to use INLINE[2] instead of INLINE which
156means inline the function early in phase-2. This may sometimes be
157needed on the because the performance of several combinators in streamly
158depends on getting inlined in phase-2 and if you use a plain `INLINE`
159annotation GHC may decide to delay the inlining in some cases. This is
160not very common but may be needed sometimes. Perhaps GHC can be fixed or
161we can resolve this using fusion-plugin in future.
163### Delayed INLINE
165When a function is passed to a higher order function e.g. a function
166passed to `concatMap` or `concatUnfold` then we want the function to be
167inlined after the higher order is inlined so that proper fusion of the
168higher order function can occur. For such cases we usually add INLINE[1]
169on the function being passed to instruct GHC not to inline it too early.
171## Strictness annotations
173* Strictness annotations on data, specially the data used as accumulator in
174 folds and scans, can help in improving performance.
175* Strictness annotations on function arguments can help the compiler unbox
176 constructors in certain cases, improving performance.
177* Sometimes using `-XStrict` extension can help improve performance, if so you
178 may be missing some strictness annotations. `-XStrict` can be used as an aid
179 to detect missing annotations, using it blindly may regress performance.
181## Use tail recursion
183Do not use a strict `foldr` or lazy `foldl` unless you know what you are
184doing. Use lazy `foldr` for lazily transforming the stream and strict
185`foldl` for reducing the stream. If you are manually writing recursive
186code, try to use tail recursion where possible.
188# Build times and space considerations
190Haskell, being a pure functional language, confers special powers on
191GHC. It allows GHC to do whole program optimization. In a closed loop
192all the components of the loop are inlined and GHC fuses them together,
193performs many optimizing transformations and churns out an optimized
194fused loop code. Let's call it whole-loop-optimization.
196To be able to fuse the loop by whole-loop-optimization all the parts of the
197loop must be operated on by GHC at the same time to fuse them together. The
198amount of time and memory required to do so depends on the size of the loop.
199Huge loops can take a lot of time and memory. We have seen GHC take 4-5 GB of
200memory when a lot of combinators are used in a single module.
202If a module takes too much time and space we can break it into multiple
203modules moving some non-inlined parts in another module. There is
204another advantage of breaking large modules, it can take advantage of
205parallel compilation if they do not depend on each other.