@@ -14,6 +14,9 @@
14-- Please refer to "Streamly.Internal.Data.Stream" for more functions that have
14-- Please refer to "Streamly.Internal.Data.Stream" for more functions that have
15-- not yet been released.
15-- not yet been released.
17+-- For continuation passing style (CPS) stream type, please refer to
18+-- the "Streamly.Data.StreamK" module.
17-- Checkout the <https://github.com/composewell/streamly-examples>
20-- Checkout the <https://github.com/composewell/streamly-examples>
18-- repository for many more real world examples of stream programming.
21-- repository for many more real world examples of stream programming.
@@ -501,30 +504,76 @@ import Prelude
501-- "Data.List" like functions and many more powerful combinators to perform
504-- "Data.List" like functions and many more powerful combinators to perform
502-- common programming tasks.
505-- common programming tasks.
504−-- == Performance Notes
507+-- == Stream Fusion
506−-- The 'Stream' type represents a stream by composing state as data which
509+-- The fused 'Stream' type employs stream fusion for C-like performance when
507−-- enables stream fusion. Stream fusion generates a tight loop without any
510+-- looping over data. It represents a stream source or transformation by
508−-- constructor allocations between the stages, providing C like performance for
511+-- defining a state machine with explicit state, and a step function working on
512+-- the state. A typical stream operation consumes elements from the previous
513+-- state machine in the pipeline, transforms them and yields new values for the
514+-- next stage to consume. The stream operations are modular and represent a
515+-- single task, they have no knowledge of previous or next operation on the
518+-- A typical stream pipeline consists of a stream producer, several stream
519+-- transformation operations and a stream consumer. All these operations taken
520+-- together form a closed loop processing the stream elements. Elements are
521+-- transferred between stages using a boxed data constructor. However, all the
522+-- stages of the pipeline are fused together by GHC, eliminating the
523+-- intermediate constructors, and thus forming a tight C like loop without any
524+-- boxed data being used in the loop.
509−-- the loop. Stream fusion works when multiple functions are combined in a
526+-- Stream fusion works effectively when:
510−-- pipeline statically. Therefore, the operations in this module must be
527+--
511−-- inlined and must not be used recursively to allow for stream fusion.
528+-- * the stream pipeline is composed statically (known at compile time)
512−--
529+-- * all the operations forming the loop are inlined
513−-- Using the 'Stream' type binary stream construction operations like 'cons',
530+-- * the loop is not recursively defined, recursion breaks inlining
514−-- 'append' etc. degrade quadratically (O(n^2)) when combined many times. If
531+--
515−-- you need to combine these operations, say more than 50 times in a single
516−-- loop, then you should use the continuation style stream type 'StreamK'
532+-- If these conditions cannot be met, the CPS style stream type 'StreamK' may
517−-- instead. Also, if you need to use these operations in a recursive loop you
533+-- turn out to be a better choice than the fused stream type 'Stream'.
518−-- should use 'StreamK' instead.
534+--
519−--
535+-- == Stream vs StreamK
520−-- The 'StreamK' type represents a stream by composing function calls,
536+--
521−-- therefore, a function call overhead is incurred at each composition. It is
537+-- The fused stream model avoids constructor allocations or function call
522−-- quite fast in general but may be a few times slower than a fused stream.
538+-- overheads. However, the stream is represented as a state machine and to
523−-- However, it allows for scalable dynamic composition and control flow
539+-- generate elements it has to navigate the decision tree of the state machine.
524−-- manipulation. Using the 'StreamK' type binary operations like 'cons' and
540+-- Moreover, the state machine is cranked for each element in the stream. This
525−-- 'append' provide linear (O(n)) performance.
541+-- performs extremely well when the number of states are limited. The state
542+-- machine starts getting expensive as the number of states increase. For
543+-- example, generating a million element stream from a list requires a single
544+-- state and is very efficient. However, using fused 'cons' to generate a
545+-- million element stream would be a disaster.
547+-- A typical worst case scenario for fused stream model is a large number of
548+-- `cons` or `append` operations. A few static `cons` or `append` operations
549+-- are very fast and much faster than a CPS style stream. However, if we
550+-- construct a large stream using `cons` it introduces as many states in the
551+-- state machine as the number of elements. If we compose the `cons` as a
552+-- binary tree it will take @n * log n@ time to navigate the tree, and @n * n@
553+-- if it is a right associative composition.
555+-- For quadratic cases of fused stream, after a certain threshold the CPS
556+-- stream would perform much better and exhibit linear performance behavior.
557+-- Operations like 'cons' or 'append'; are typically recursively called to
558+-- construct a lazy infinite stream. For such use cases the CPS style 'StreamK'
559+-- type is provided. CPS streams do not have a state machine that needs to be
560+-- cranked for each element, past state has no effect on the future element
561+-- processing. However, it incurs a function call overhead for each operation
562+-- for each element, which could be very large overhead compared to fused state
563+-- machines even if it has many states and cranks it for each element. But in
564+-- some cases scales tip in favor of the CPS stream. In those cases even though
565+-- CPS has a large constant overhead, it has a linear performance rather than
568+-- As a general guideline, if you have to use 'cons' or 'append' or operations
569+-- of similar nature, at a large scale, then 'StreamK' should be used. When you
570+-- need to compose the stream dynamically or recursively, then 'StreamK' should
571+-- be used. Typically you would use a dynamically generated 'StreamK' with
572+-- chunks of data which can then be processed by statically fused stream
573+-- pipeline operations.
527−-- 'Stream' and 'StreamK' types can be interconverted.
575+-- 'Stream' and 'StreamK' types can be interconverted. See
576+-- "Streamly.Data.StreamK" module for conversion operations.
529-- == Useful Idioms
578-- == Useful Idioms