on master help sign in
Add NOINLINE optimization strategy
commit7755f29424b14f65f600991859b50efaa7600717 authorHarendra Kumar <harendra@composewell.com> dateTue 27 Oct 2020, 01:04 (6 years ago) parent016eef1
1 file changed+28−5
design/optimization-guidelines.md+28−5
design/optimization-guidelines.md+28−5
@@ -1,12 +1,12 @@
11# Guidelines for writing well optimized code
22
3−Stream operations are always part of a loop. Usually the loop consiste of a
3+Stream operations are always part of a loop. Usually the loop consists of a
44stream generation or an unfold operation followed by stream transformation
55functions (e.g. map) and then a stream elimination operation or a fold
66operation.
77
88The default or most common stream representation used in streamly is `StreamD`
9−which is a direct style stream reprensentation. All the direct style
9+which is a direct style stream representation. All the direct style
1010operations in a loop "fuse" together to form a tight machine loop eliminating
1111any intermediate constructors, therefore, reducing allocations and cpu cost.
1212This elimination of intermediate constructors in a loop is known as stream
@@ -74,11 +74,34 @@ The step function of a Fold or Parser:
7474
7575* Sometimes you may need an explicit INLINE on the step function.
7676
77+## NOINLINE, isolating the closed loop
78+
79+We want the loop iterations to be optimized and the loop stages to be fused to
80+generate a tighter loop. However, it is not necessarily optimal to inline the
81+whole loop itself into a parent function. For example, consider the following
82+function:
83+
84+```
85+{-# NOINLINE readWriteAfter_Stream #-}
86+readWriteAfter_Stream :: Handle -> Handle -> IO ()
87+readWriteAfter_Stream inh devNull =
88+ let readEx = IP.after_ (hClose inh) (S.unfold FH.read inh)
89+ in S.fold (FH.write devNull) readEx
90+```
91+
92+If this is inlined into a parent benchmark group list, this leads to
93+many times performance degradation. That's because inlining the loop
94+into a bigger structure may interfere with the optimization of the loop
95+itself and it may not fuse. Whereas it is desirable to INLINE all
96+the stages of a loop, it is often not useful to inline the whole loop
97+itself, in fact we may have to occasionally use a `NOINLINE` so that the
98+compiler does not inline it.
99+
77100## StreamK operations
78101
79102StreamK uses foldr/build fusion to a very limited degree. StreamK is not the
80103primary representation in streamly but is used for several operations that
81−cannot scale in StreamD represenation. StreamK is relatively immune to compiler
104+cannot scale in StreamD representation. StreamK is relatively immune to compiler
82105optimizations. In some cases you may need an INLINE pragma to improve the
83106performance.
84107
@@ -182,14 +205,14 @@ enough to achieve that. In general, we should try to ensure that higher order
182205functions are inlined before or in the same phase as the functions they can
183206consume as arguments. This means `StreamD` combinators should not be marked
184207as `INLINE` or `INLINE_EARLY`, instead they should all be marked as
185−`INLINE_NORMAL` because higher order funcitons like `concatMap`/`map`/`mapM`
208+`INLINE_NORMAL` because higher order functions like `concatMap`/`map`/`mapM`
186209etc are marked as `INLINE_NORMAL`. `StreamD` functions in other modules like
187210`Streamly.Data.Array.Storable.Foreign` should also follow the same rules.
188211
189212## Stream Fusion
190213
191214In StreamD combinators, inlining the inner step or loop functions too early
192−i.e. in the same pahse or before the outer function is inlined may block stream
215+i.e. in the same phase or before the outer function is inlined may block stream
193216fusion opportunities. Therefore, the inner step functions and folding loops are
194217marked as INLINE_LATE.
195218