Fake threads (was [Python-Dev] ActiveState & fork & Perl)

Tim Peters tim_one@email.msn.com
Tue, 13 Jul 1999 00:03:33 -0400


Backtracking a bit:

[Guido]
> This is another key description of continuations (maybe not quite
> worth a hug :).

I suppose a kiss is out of the question, then.

> The continuation captures exactly all state that is represented by
> "position in the program" and no state that is represented by variables.

Right!

> But there are many hairy details.  In antiquated assembly, there might
> not be a call stack, and a continuation could be represented by a
> single value: the program counter.  But now we have a call stack, a
> value stack, a block stack (in Python) and who knows what else.
>
> I'm trying to understand whether we can get away with saving just a
> pointer to a frame, whether we need to copy the frame, or whether we
> need to copy the entire frame stack.

As you convinced yourself in following paragraphs, for 1st-class
continuations "the entire frame stack" *may* be necessary.

> ...
> How does Scheme do this?

I looked up R. Kent Dybvig's doctoral dissertation, at

    ftp://ftp.cs.indiana.edu/pub/scheme-repository/doc/pubs/3imp.ps.gz

He gives detailed explanations of 3 Scheme implementations there (from
whence "3imp", I guess).  The first is all heap-based, and looks much like
the simple Wilson implementation I summarized yesterday.  Dybvig profiled it
and discovered it spent half its time in, together, function call overhead
and name resolution.

So he took a different approach:  Scheme is, at heart, just another
lexically scoped language, like Algol or Pascal.  So how about implementing
it with a perfectly conventional shared, contiguous stack?  Because that
doesn't work:  the advanced features (lexical closures with indefinite
extent, and user-captured continuations) aren't stack-like.  Tough, forget
those at the start, and do whatever it takes later to *make* 'em work.

So he did.  When his stack implementation hit a user's call/cc, it made a
physical copy of the entire stack.  And everything ran much faster!  He
points out that "real programs" come in two flavors:

1) Very few, or no, call/cc thingies.  Then most calls are no worse than
Algol/Pascal/C functions, and the stack implementation runs them at
Algol/Pascal/C speed (if we knew of anything faster than a plain stack, the
latter would use it).

2) Lots of call/cc thingies.  Then "the stack" is likely to be shallow (the
program is spending most of its time co-transferring, not recursing deeply),
and because the stack is contiguous he can exploit the platform's fastest
block-copy operation (no need to chase pointer links, etc).

So, in some respects, Dybvig's stack implementation of Scheme was more
Pythonic than Python's current implementation <wink -- Dybvig effectively
used a Python list at this level, while Python effectively uses a Lispish
cons'ed frame list>.

His third implementation was for some propeller-head theoretical "string
machine", so I won't even mention it.

worrying-about-the-worst-case-can-hurt-the-normal-cases-ly y'rs  - tim