1# Build and Optimization Guide
5### Compiler (GHC) Versions
7GHC 8.6 and above are recommended. For best performance use GHC 8.8 or
88.10 along with `fusion-plugin` (see below). Benchmarks show that GHC
98.8 has significantly better performance than GHC 8.6 in many cases.
11GHC 9.0 has some performance issues, please see [this
12issue](https://github.com/composewell/streamly/issues/1061) for details.
13However, upcoming minor version updates may fix some of these issues.
15### Memory requirements
17Building streamly itself may require upto 4GB memory. Depending on the
18size of the application you may require 1-16GB memory to build. For most
19applications up to 8GB of memory should be sufficient.
21To reduce the memory footprint you may want to break big modules into
22smaller ones and reduce unnecessary inlining on large functions. You can
23also use the `-Rghc-timing` GHC option to report the memory usage during
26See the "Build times and space considerations" section below for more
29### Compilation Options
31#### Recommended Options
33Add `fusion-plugin` to the `build-depends` section of your program in
34the cabal file and use the following GHC options:
40 -fspec-constr-recursive=16
41 -fplugin Fusion.Plugin
461. [fusion-plugin](https://hackage.haskell.org/package/fusion-plugin) can
47 improve performance significantly by better stream fusion, many
48 cases. If the perform regresses due to fusion-plugin please open
49 an issue. You may remove the `-fplugin` option for regular builds
50 but it is recommended for deployment builds and performance
51 benchmarking. Note, for GHC 8.4 or lower fusion-plugin cannot be used.
522. In certain cases it is possible that GHC takes too long to compile
53 with `-fspec-constr-recursive=16`, if that happens please reduce the
54 value or remove that option.
553. At the very least `-O -fdicts-strict` compilation options are
56 absolutely required to avoid issues in some cases. For example, the
57 program `main = S.drain $ S.concatMap S.fromList $ S.repeat []` may
58 hog memory without these options.
60See [Explanation](#explanation) for details about these flags.
64* `-fdicts-strict` is needed to avoid [a GHC
65issue](https://gitlab.haskell.org/ghc/ghc/issues/17745) leading to
66memory leak in some cases.
68* `-fspec-constr-recursive` is needed for better stream fusion by enabling
69the `SpecConstr` optimization in more cases. Large values used with this flag
70may lead to huge compilation times and code bloat, if that happens please avoid
71it or use a lower value (e.g. 3 or 4).
73* `-fmax-worker-args` is needed for better stream fusion by enabling the
74`SpecConstr` optimization in some important cases.
76* `-fplugin=Fusion.Plugin` enables predictable stream fusion
77optimization in certain cases by helping the compiler inline internal
78bindings and therefore enabling case-of-case optimization. In some
79cases, especially in some file IO benchmarks, it can make a difference of
805-10x better performance.
82### Multi-core Parallelism
84Concurrency without a threaded runtime may be a bit more efficient. Do not use
85threaded runtime unless you really need multi-core parallelism. To get
86multi-core parallelism use the following GHC options:
88 `-threaded -with-rtsopts "-N"`
90## Platform Specific Features
92Streamly supports Linux, macOS and Windows operating systems. Some
93modules and functionality may depend on specific OS kernel features.
94Features/modules may get disabled if the kernel/OS does not support it.
98* File system events notification module is supported only for kernel versions
103* File system events notification module requires macOS 10.7+ with
104 Xcode/macOS SDK installed (depends on `Cocoa` framework). However, we only
105 test on latest three versions of the OS.
107## Performance Optimizations
109A "closed loop" is any streamly code that generates a stream using
110unfold (or conceptually any stream generation combinator) and ends
111up eliminating it with a fold (conceptually any stream elimination
112combinator). It is essentially a loop processing multiple elements in
113a stream sequence, just like a `for` or `while` loop in imperative
116Closed loops are generated in a modular fashion by stream generation,
117transformation and elimination combinators in streamly. Combinators
118transfer data to the next stream pipeline stage using data constructors.
119These data constructors are eliminated by the compiler using `stream
120fusion` optimizations, generating a very efficient loop.
122However, stream fusion optimization depends on proper inlining of the
123combinators involved. The fusion-plugin package mentioned earlier
124fills gaps for several optimizations that GHC does not perform
125automatically. It automatically inlines the internal definitions
126that involve the constructors we want to eliminate. In some cases
127fusion-plugin may not help and programmer may have to annotate the code
128manually for complete fusion. In this section we mention some of the
129cases where programmer annotation may help in stream fusion.
131Remember, you need to worry about performance only where it matters, try
132to optimize the fast path and not everything blindly.
134### INLINE annotations
136It may help to add INLINE annotations on any intermediate functions
137involved in a closed loop. In some cases you may have to add an inline
138phase as well as described below.
140Usually GHC has three inline phases - the first phase is pahse-2, the
141second phase is phase-1 and the last one is phase-0.
145Generally, you only have to inline the combinators or functions
146participating in a loop and not the whole loop itself. But sometimes
147you may want to inline the whole loop itself inside a larger
148function. In most cases you can just add an INLINE pragma on
149the function containing the loop. But you may need some special
150considerations in some (not common) cases.
152In some cases you may have to use INLINE[2] instead of INLINE which
153means inline the function early in phase-2. This may sometimes be
154needed on the because the performance of several combinators in streamly
155depends on getting inlined in phase-2 and if you use a plain `INLINE`
156annotation GHC may decide to delay the inlining in some cases. This is
157not very common but may be needed sometimes. Perhaps GHC can be fixed or
158we can resolve this using fusion-plugin in future.
162When a function is passed to a higher order function e.g. a function
163passed to `concatMap` or `unfoldMany` then we want the function to be
164inlined after the higher order is inlined so that proper fusion of the
165higher order function can occur. For such cases we usually add INLINE[1]
166on the function being passed to instruct GHC not to inline it too early.
168### Strictness annotations
170* Strictness annotations on data, specially the data used as accumulator in
171 folds and scans, can help in improving performance.
172* Strictness annotations on function arguments can help the compiler unbox
173 constructors in certain cases, improving performance.
174* Sometimes using `-XStrict` extension can help improve performance, if so you
175 may be missing some strictness annotations. `-XStrict` can be used as an aid
176 to detect missing annotations, using it blindly may regress performance.
178### Use tail recursion
180Do not use a strict `foldr` or lazy `foldl` unless you know what you are
181doing. Use lazy `foldr` for lazily transforming the stream and strict
182`foldl` for reducing the stream. If you are manually writing recursive
183code, try to use tail recursion where possible.
185## Build times and space considerations
187Haskell, being a pure functional language, confers special powers on
188GHC. It allows GHC to do whole program optimization. In a closed loop
189all the components of the loop are inlined and GHC fuses them together,
190performs many optimizing transformations and churns out an optimized
191fused loop code. Let's call it whole-loop-optimization.
193To be able to fuse the loop by whole-loop-optimization all the parts of the
194loop must be operated on by GHC at the same time to fuse them together. The
195amount of time and memory required to do so depends on the size of the loop.
196Huge loops can take a lot of time and memory. We have seen GHC take 4-5 GB of
197memory when a lot of combinators are used in a single module.
199If a module takes too much time and space we can break it into multiple
200modules moving some non-inlined parts in another module. There is
201another advantage of breaking large modules, it can take advantage of
202parallel 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