on master help sign in
Split into separate build and optimization guides
commit23082b661f9255d0a78b0cfd555245862631239b authorHarendra Kumar <harendra@composewell.com> dateWed 16 Jun 2021, 16:23 (5 years ago) parentcab6ab9
2 files changed+99−98
docs/Build.md+1−98 docs/optimizing.md+98
docs/Build.md+1−98
@@ -1,4 +1,4 @@
1−# Build and Optimization Guide
1+# Build Guide
22
33## Building
44
@@ -103,100 +103,3 @@ Features/modules may get disabled if the kernel/OS does not support it.
103103* File system events notification module requires macOS 10.7+ with
104104 Xcode/macOS SDK installed (depends on `Cocoa` framework). However, we only
105105 test on latest three versions of the OS.
106−
107−## Performance Optimizations
108−
109−A "closed loop" is any streamly code that generates a stream using
110−unfold (or conceptually any stream generation combinator) and ends
111−up eliminating it with a fold (conceptually any stream elimination
112−combinator). It is essentially a loop processing multiple elements in
113−a stream sequence, just like a `for` or `while` loop in imperative
114−programming.
115−
116−Closed loops are generated in a modular fashion by stream generation,
117−transformation and elimination combinators in streamly. Combinators
118−transfer data to the next stream pipeline stage using data constructors.
119−These data constructors are eliminated by the compiler using `stream
120−fusion` optimizations, generating a very efficient loop.
121−
122−However, stream fusion optimization depends on proper inlining of the
123−combinators involved. The fusion-plugin package mentioned earlier
124−fills gaps for several optimizations that GHC does not perform
125−automatically. It automatically inlines the internal definitions
126−that involve the constructors we want to eliminate. In some cases
127−fusion-plugin may not help and programmer may have to annotate the code
128−manually for complete fusion. In this section we mention some of the
129−cases where programmer annotation may help in stream fusion.
130−
131−Remember, you need to worry about performance only where it matters, try
132−to optimize the fast path and not everything blindly.
133−
134−### INLINE annotations
135−
136−It may help to add INLINE annotations on any intermediate functions
137−involved in a closed loop. In some cases you may have to add an inline
138−phase as well as described below.
139−
140−Usually GHC has three inline phases - the first phase is pahse-2, the
141−second phase is phase-1 and the last one is phase-0.
142−
143−#### Early INLINE
144−
145−Generally, you only have to inline the combinators or functions
146−participating in a loop and not the whole loop itself. But sometimes
147−you may want to inline the whole loop itself inside a larger
148−function. In most cases you can just add an INLINE pragma on
149−the function containing the loop. But you may need some special
150−considerations in some (not common) cases.
151−
152−In some cases you may have to use INLINE[2] instead of INLINE which
153−means inline the function early in phase-2. This may sometimes be
154−needed on the because the performance of several combinators in streamly
155−depends on getting inlined in phase-2 and if you use a plain `INLINE`
156−annotation GHC may decide to delay the inlining in some cases. This is
157−not very common but may be needed sometimes. Perhaps GHC can be fixed or
158−we can resolve this using fusion-plugin in future.
159−
160−#### Delayed INLINE
161−
162−When a function is passed to a higher order function e.g. a function
163−passed to `concatMap` or `unfoldMany` then we want the function to be
164−inlined after the higher order is inlined so that proper fusion of the
165−higher order function can occur. For such cases we usually add INLINE[1]
166−on the function being passed to instruct GHC not to inline it too early.
167−
168−### Strictness annotations
169−
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.
177−
178−### Use tail recursion
179−
180−Do not use a strict `foldr` or lazy `foldl` unless you know what you are
181−doing. Use lazy `foldr` for lazily transforming the stream and strict
182−`foldl` for reducing the stream. If you are manually writing recursive
183−code, try to use tail recursion where possible.
184−
185−## Build times and space considerations
186−
187−Haskell, being a pure functional language, confers special powers on
188−GHC. It allows GHC to do whole program optimization. In a closed loop
189−all the components of the loop are inlined and GHC fuses them together,
190−performs many optimizing transformations and churns out an optimized
191−fused loop code. Let's call it whole-loop-optimization.
192−
193−To be able to fuse the loop by whole-loop-optimization all the parts of the
194−loop must be operated on by GHC at the same time to fuse them together. The
195−amount of time and memory required to do so depends on the size of the loop.
196−Huge loops can take a lot of time and memory. We have seen GHC take 4-5 GB of
197−memory when a lot of combinators are used in a single module.
198−
199−If a module takes too much time and space we can break it into multiple
200−modules moving some non-inlined parts in another module. There is
201−another advantage of breaking large modules, it can take advantage of
202−parallel compilation if they do not depend on each other.
docs/optimizing.mdnew file+98
@@ -0,0 +1,98 @@
1+# Optimization Guide
2+
3+## Performance Optimizations
4+
5+A "closed loop" is any streamly code that generates a stream using
6+unfold (or conceptually any stream generation combinator) and ends
7+up eliminating it with a fold (conceptually any stream elimination
8+combinator). It is essentially a loop processing multiple elements in
9+a stream sequence, just like a `for` or `while` loop in imperative
10+programming.
11+
12+Closed loops are generated in a modular fashion by stream generation,
13+transformation and elimination combinators in streamly. Combinators
14+transfer data to the next stream pipeline stage using data constructors.
15+These data constructors are eliminated by the compiler using `stream
16+fusion` optimizations, generating a very efficient loop.
17+
18+However, stream fusion optimization depends on proper inlining of the
19+combinators involved. The fusion-plugin package mentioned earlier
20+fills gaps for several optimizations that GHC does not perform
21+automatically. It automatically inlines the internal definitions
22+that involve the constructors we want to eliminate. In some cases
23+fusion-plugin may not help and programmer may have to annotate the code
24+manually for complete fusion. In this section we mention some of the
25+cases where programmer annotation may help in stream fusion.
26+
27+Remember, you need to worry about performance only where it matters, try
28+to optimize the fast path and not everything blindly.
29+
30+### INLINE annotations
31+
32+It may help to add INLINE annotations on any intermediate functions
33+involved in a closed loop. In some cases you may have to add an inline
34+phase as well as described below.
35+
36+Usually GHC has three inline phases - the first phase is pahse-2, the
37+second phase is phase-1 and the last one is phase-0.
38+
39+#### Early INLINE
40+
41+Generally, you only have to inline the combinators or functions
42+participating in a loop and not the whole loop itself. But sometimes
43+you may want to inline the whole loop itself inside a larger
44+function. In most cases you can just add an INLINE pragma on
45+the function containing the loop. But you may need some special
46+considerations in some (not common) cases.
47+
48+In some cases you may have to use INLINE[2] instead of INLINE which
49+means inline the function early in phase-2. This may sometimes be
50+needed on the because the performance of several combinators in streamly
51+depends on getting inlined in phase-2 and if you use a plain `INLINE`
52+annotation GHC may decide to delay the inlining in some cases. This is
53+not very common but may be needed sometimes. Perhaps GHC can be fixed or
54+we can resolve this using fusion-plugin in future.
55+
56+#### Delayed INLINE
57+
58+When a function is passed to a higher order function e.g. a function
59+passed to `concatMap` or `unfoldMany` then we want the function to be
60+inlined after the higher order is inlined so that proper fusion of the
61+higher order function can occur. For such cases we usually add INLINE[1]
62+on the function being passed to instruct GHC not to inline it too early.
63+
64+### Strictness annotations
65+
66+* Strictness annotations on data, specially the data used as accumulator in
67+ folds and scans, can help in improving performance.
68+* Strictness annotations on function arguments can help the compiler unbox
69+ constructors in certain cases, improving performance.
70+* Sometimes using `-XStrict` extension can help improve performance, if so you
71+ may be missing some strictness annotations. `-XStrict` can be used as an aid
72+ to detect missing annotations, using it blindly may regress performance.
73+
74+### Use tail recursion
75+
76+Do not use a strict `foldr` or lazy `foldl` unless you know what you are
77+doing. Use lazy `foldr` for lazily transforming the stream and strict
78+`foldl` for reducing the stream. If you are manually writing recursive
79+code, try to use tail recursion where possible.
80+
81+## Build times and space considerations
82+
83+Haskell, being a pure functional language, confers special powers on
84+GHC. It allows GHC to do whole program optimization. In a closed loop
85+all the components of the loop are inlined and GHC fuses them together,
86+performs many optimizing transformations and churns out an optimized
87+fused loop code. Let's call it whole-loop-optimization.
88+
89+To be able to fuse the loop by whole-loop-optimization all the parts of the
90+loop must be operated on by GHC at the same time to fuse them together. The
91+amount of time and memory required to do so depends on the size of the loop.
92+Huge loops can take a lot of time and memory. We have seen GHC take 4-5 GB of
93+memory when a lot of combinators are used in a single module.
94+
95+If a module takes too much time and space we can break it into multiple
96+modules moving some non-inlined parts in another module. There is
97+another advantage of breaking large modules, it can take advantage of
98+parallel compilation if they do not depend on each other.