1
1
.. _Lambda Lifting Chapter :
2
2
3
+ Local Variables
4
+ .. |glift | replace :: ``g_lifted ``
5
+
3
6
`Lambda Lifting `
4
7
================
5
8
6
9
Lambda Lifting :cite:p: `lambdaLifting ` is a classic rewriting technique that
7
- rewrites functions to avoid excess allocations. It optimizes function
8
- definitions by moving local functions to the global scope of the program;
9
- thereby closure allocations, and by adding parameters to the function definition
10
- to capture free variables.
10
+ that avoids excess closure allocations. It avoids closure allocation by moving
11
+ local functions to the global scope of the program, and by adding parameters to
12
+ the function definition to capture free variables. Thus, when a lifted function
13
+ is called no heap allocation is needed because the lifted function no longer
14
+ contains closures, rather it only references global names.
11
15
12
16
A Working Example
13
17
-----------------
14
18
15
19
Consider the following program [# ]_:
16
20
17
- .. exec ::
18
- :context: true
19
- :process: haskell
20
-
21
- module Main where
21
+ .. code-block :: haskell
22
22
23
23
f :: Int -> Int -> Int
24
24
f a 0 = a
@@ -27,17 +27,55 @@ Consider the following program [#]_:
27
27
g 0 = a
28
28
g n = 1 + g (n - 1)
29
29
30
- main :: IO ()
31
- main = print $ f 10 100
30
+ The function ``f `` defines one local function, ``g ``, which appears as a free
31
+ variable in ``f ``. Similarly, the variable ``a `` is a free variable in ``g ``. A
32
+ lambda lifted ``g ``, will convert all free variables in ``g `` to parameters.
33
+ Thus |glift | turns into:
34
+
35
+ .. code-block :: haskell
36
+
37
+ g_lifted a 0 = a
38
+ g_lifted a n = 1 + g (n - 1)
39
+
40
+ Now ``a `` is an input, which means that |glift | can be floated out of ``f ``
41
+ to the top level producing the final program:
42
+
43
+ .. code-block :: haskell
44
+
45
+ g :: Int -> Int -> Int
46
+ g_lifted a 0 = a
47
+ g_lifted a n = 1 + g (n - 1)
48
+
49
+ f :: Int -> Int -> Int
50
+ f a 0 = a
51
+ f a n = f (g_lifted a (n `mod` 2)) (n - 1)
52
+
53
+ This new program will be much faster because ``f `` becomes essentially
54
+ non-allocating. Before the lambda lifting transformation ``f `` had to allocate a
55
+ closure for ``g `` in order to pass ``a `` to ``g ``. After the lambda lifting on
56
+ ``g `` this is no longer the case; |glift | is a top level function so ``f `` can
57
+ simply reference it; no closures needed!
58
+
59
+ .. note ::
60
+
61
+ The fundamental tradeoff is decreased heap allocation for an increase in
62
+ function parameters at each call site. This means that lambda lifting is not
63
+ always a performance win. See `When to Manually Apply Lambda Lifting `_ for
64
+ guidance on recognizing when your program may benefit.
32
65
33
66
34
67
How Lambda Lifting Works in GHC
35
68
-------------------------------
36
69
70
+ GHC does have a lambda lifting pass in STG, however lambda lifting is not the
71
+ default method GHC uses for handling local functions. GHC uses an alternative
72
+ strategy called :term: `Closure Conversion `...
73
+
37
74
38
75
Observing the Effect of Lambda Lifting
39
76
--------------------------------------
40
77
78
+
41
79
When to Manually Apply Lambda Lifting
42
80
-------------------------------------
43
81
@@ -88,5 +126,5 @@ and we can also run from cabal target!!
88
126
:args: bench lethargy:tooManyClosures
89
127
90
128
91
- .. [# ] This program comes from Sebastian Graf and Simon Peyton Jones
129
+ .. [# ] This program and example comes from Sebastian Graf and Simon Peyton Jones
92
130
:cite:p: `selectiveLambdaLifting `; thank you for your labor!:
0 commit comments