Fiber implements an proof-of-concept Python decorator that rewrites a plan
so that it could per chance most likely perchance presumably even be paused and resumed (by shifting stack variables to a heap frame
and at the side of if statements to simulate jumps/gotos to specific strains of code).
Then, the employ of a trampoline plan that simulates the name stack on the heap, we
can name functions that recurse arbitrarily deeply with out stack overflowing
(assuming we net now not scuttle out of heap memory).
Featured Content Ads
add advertising hereFeatured Content Ads
add advertising hereTOC
The most sensible likely plan it works
A transient refresher on the name stack: assuredly, when some plan A calls
yet any other plan B, A is “paused” whereas B runs to completion. Then, as soon as B
finishes, A is resumed.
In repeat to pass the name stack to the heap, we like now to transform plan A
to (1) store all variables on the heap, and (2) be in a field to resume execution
at specific strains of code for the period of the plan.
The main step is discreet: we rewrite all native hundreds and shops to as an change load
and store in a frame dictionary that’s handed into the plan. The 2nd is
extra complicated: because Python would not give a take to goto statements, we like now to
insert if statements to skip the code prefix that we net now not wish to create.
There are a form of “special
kinds”
that can perchance now not be jumped into. These we should address by rewriting them precise into a develop
that we net address.
Featured Content Ads
add advertising hereTo illustrate, if we recursively name a plan interior a for loop, we would treasure
in tell to resume execution on the identical iteration. On the opposite hand, when Python
executes a for loop on an non-iterator iterable this could occasionally maintain a new iterator
every time. To address this case, we rewrite for loops into the an identical whereas
loop. Equally, we should rewrite boolean expressions that fast circuit (and
,
or
) into the an identical if statements.
Lastly, we should replace all recursive calls and widespread returns by as an change
returning an instruction to a trampoline to name the newborn plan or return
the worth to the mother or father plan, respectively.
To recap, right here are the AST passes we for the time being put in power:
- Rewrite special kinds:
for_to_while
: Transforms for loops into the an identical whereas loops.promote_while_cond
: Rewrites the whereas conditional to employ a non everlasting
variable that’s up to this point every loop iteration so that we are able to management when
it’s miles evaluated (e.g. if the loop situation involves a recursive name).bool_exps_to_if
: Convertsand
andor
expressions into the
an identical if statements.
promote_to_temporary
: Assigns the outcomes of recursive calls into
non everlasting variables. This is excessive when we maintain extra than one recursive calls
in the identical assertion (e.g.fib(n-1) + fib(n-2)
): we like now to resume
execution in the center of the expression.remove_trivial_temporaries
: Removes temporaries that are assigned to advantageous
as soon as and are at as soon as assigned to a pair of alternative variable, replacing subsequent
usages with that other variable. This helps us detect tail calls.insert_jumps
: Marks the assertion after yield capabilities (for the time being recursive
calls and widespread returns) with apocket e-book computer
index, and inserts if statements so
that re-execution of the plan will resume at that program counter.lift_locals_to_frame
: Replaces hundreds and shops of native variables to
hundreds and shops in the frame object.add_trampoline_returns
: Replaces areas the place we should yield (recursive
calls and widespread returns) with returns to the trampoline plan.fix_fn_def
: Rewrites the plan defintion to rob aframe
parameter.
Seek the examples
directory for functions and the outcomes after
every AST drag. Additionally, leer src/trampoline_test.py
for
some take a look at cases.
Performance
A easy tail-recursive plan that computes the sum of an array takes about
10-11 seconds to compute with Fiber. 1000 iterations of the an identical for loop
takes 7-8 seconds to compute. So we’re slower by roughly a a part of 1000.
lst = checklist(differ(1, 100001)) # fiber @fiber.fiber(locals=locals()) def sum(lst, acc): if now not lst: return acc return sum(lst[1:], acc + lst[0]) # for loop total = 0 for i in lst: total += i print(total, trampoline.scuttle(sum, [lst, 0])) # 5000050000, 5000050000
We could per chance reinforce the efficiency of the code by eliminating redundant if
tests in the generated code. Additionally, as we statically know the stack variables,
we are able to employ an array for the stack frame and integer indexes (in decision to a
dictionary and string hashes + lookups). This could increasingly perchance like to enhance the efficiency
seriously, nonetheless there’ll restful potentially be a enormous amount of overhead.
One more efficiency enchancment is to inline the stack array: in decision to
storing a checklist of frames in the trampoline, we would variables at as soon as in the
stack. All over again, we are able to compute the frame dimension statically. Per some assessments in
a handwritten JavaScript implementation, this has the aptitude to tempo up the
code by roughly a a part of 2-3, on the worth of a extra complicated implementation.
Obstacles
-
The transformation works on the AST stage, so we net now not give a take to other
decorators (as an illustration, we are able to now not employ
functools.cache
in the above Fibonacci instance). -
The plan can advantageous access variables that are handed in the
locals=
argument. Attributable to this, to resolve recursive plan calls,
we assist a world mapping of all fiber functions by name. This design that
fibers have to love trot names. -
We net now not give a take to a pair of special kinds (ternaries, comprehensions). These can
with out complications be added as a rewrite transformation. -
We net now not give a take to exceptions. This could per chance require us to assist song of exception
handlers in the trampoline and insert returns to the trampoline to register
and deregister handlers. -
We net now not give a take to mills. So that you just can add give a take to, we would deserve to alter the
trampoline to accept yet any other operation form (yield) that sends a worth to the
plan that known asnext()
. Additionally, the trampoline would deserve to present a take to
extra than one name stacks.
That you just’ll want to per chance presumably presumably additionally take into consideration improvements
- Toughen take a look at protection on a pair of of the AST transformations.
remove_trivial_temporaries
could per chance presumably like a pc virus if the variable that it’s miles
changed with is reassigned to yet any other worth.
- Give a take to out of the ordinary kinds (comprehensions, mills).
- Give a take to exceptions.
- Give a take to recursive calls that net now not study the return worth.
Questions
Why did not you make employ of Python mills?
It be much less engrossing as the transformations are less complicated. Here, we’re
effectively implementing mills in userspace (i.e. now not desiring VM give a take to);
leer the resolution to the subsequent ask for why this is righteous.
Additionally, other folks like mature mills to net this; leer one most contemporary generator
instance.
Why did you write this?
-
A+ mission for CS 61A at
Berkeley.
All the plan through the direction, we created a Scheme interpreter. The extra credit
ask we to interchange tail calls in Python with a return to a trampoline,
with the plan that tail name optimization in Python would let us review
tail calls to arbitrary depth in Scheme, in fixed condominium.The take a look at cases for the ask checked whether or now not decoding tail-name
recursive functions in Scheme brought on a Python stack overflow. The employ of this
Fiber implementation, (1) with out tail name optimization in our trampoline,
we would restful be in a field to drag the take a look at cases (we upright wouldn’t employ fixed
condominium) and (2) we are able to now review any Scheme expression to arbitrary depth,
even though they’re now not in tail develop. -
The React framework has an a pc virus originate which explores a compiler transform to
rewrite JavaScript mills to a recount machine so that recursive operations
(render, reconcilation) could also be written extra with out complications. This is excessive because
some JavaScript engines restful net now not give a take to mills.This mission in total implements a tough model of that compiler transform
as a proof of concept, upright in Python.
https://github.com/fb/react/pull/18942
Contributing
Seek CONTRIBUTING.md