Based on Joel's reaction regarding my profiling attempts on exception
rates/exception handling in ABCL, I've been thinking how to do better
in the exceptions/specials restoration department.
Let me start by describing how our current exception handling/specials
restoration is structured. It should be noted that the current
strategy works the way it does because we were failing to restore
special values all over the place. This made me decide that we should
restore specials when leaving the binding block, even if that is
through non-local transfer of control, with the contrived example for
exception handlers as given in my mail earlier this month.
Looking at the profiling results, we're now correct, but probably
we're not very efficient at doing it.
This is what I've been thinking:
If a block binds special variables, there are 3 ways to exit the block:
1. through normal sequential execution of the byte code
2. through a function-local jump out of the block
3. through a non-local transfer of control (modelled using the
ControlTransfer exception in ABCL)
Case (1) is easy: we just reach the end of the binding block, at which
point there is a call to "LispThread.restoreSpecialBindings(mark);".
Case (2) is a little more involved: before jumping to the target
location the specials are restored at the jump-location. To do this,
there's a bit of logic in the compiler to find out which restore point
to use.
Case (3) is quite involved: at the call site an exception is thrown
causing the stack to be unwound. When looking at it like this, the
catch-site should restore its specials state. There's no need to do
that, currently, because all the intermediate levels restore their
state. There is at least one special case here: unwind-protect may be
executing lisp code (which needs its specials restored to the right
state) without being the final catch location. Final catch requiring
forms are quite easily determined: TAGBODY (to catch GO), BLOCK (to
catch RETURN) and CATCH (to catch THROW).
One of the major drawbacks of this scheme is that all non-local
transfers become responsible for restoring their special bindings.
BLOCK and TAGBODY forms which don't serve as non-local target are not
affected by this reasoning; they fall under case (2). CATCH can never
be considered taking only local transfers of control as it However,
the entire application may not be binding any specials at all. The
overhead involves a call at the start of each block which contains
non-local-transfer targets to snapshot the special bindings state and
to restore it *in case of a non-local transfer*. The calls involved
are markSpecialBindings() and restoreSpecialBindings(), two very short
and - I think - efficient functions.
So, there's a small drawback in applications which don't use special
bindings much. However, the potential gain in applications which do
use them is quite relevant: the example I gave before shows that a
relatively simple function binding the same special variable three
times will cause 3 separate specials-unwinding-exception blocks, each
rethrowing the exception to the next level. Thinking about it a bit
more, I realize that no additional exception blocks have to be
registered. Just only the catch/restore of specials state.
Looking at it this way, I propose that we stop restoring specials in
finally blocks, moving part of the responsibility of having correct
specials bindings to BLOCK, CATCH, TAGBODY and UNWIND-PROTECT. My
change should affect both the interpreter and the compiler, to get the
full benefit of efficiency. However, I thought I'd implement the
LET/LET* binding optimization in the compiler first, just to see how
much we benefit from this change in Maxima.
Comments?
Bye,
Erik.