[Python-Dev] End of the line

Tim Peters tim_one@email.msn.com
Sun, 25 Jul 1999 14:57:50 -0400

[Tim, notes that Perl line-at-a-time text mode input runs 3x faster than
 Python's on his platform]

And much to my surprise, it turns out Perl reads lines a character at a time
too!  And they do not reimplement stdio.  But they do cheat.

Perl's internals are written on top of an abstract IO API, with "PerlIO *"
instead of "FILE *", "PerlIO_tell(PerlIO *)" instead of "ftell(FILE*)", and
so on.  Nothing surprising in the details, except maybe that stdin is
modeled as a function "PerlIO *PerlIO_stdin(void)" instead of as global data
(& ditto for stdout/stderr).

The usual *implementation* of these guys is as straight macro substitution
to the corresponding C stdio call.  It's possible to implement them some
other way, but I don't see anything in the source that suggests anyone has
done so, except possibly to build it all on AT&T's SFIO lib.

So where's the cheating?  In these API functions:

    int     PerlIO_has_base(PerlIO *);
    int     PerlIO_has_cntptr(PerlIO *);
    int     PerlIO_canset_cnt(PerlIO *);

    char   *PerlIO_get_ptr(PerlIO *);
    int     PerlIO_get_cnt(PerlIO *);
    void    PerlIO_set_cnt(PerlIO *,int);
    void    PerlIO_set_ptrcnt(PerlIO *,char *,int);
    char   *PerlIO_get_base(PerlIO *);
    int     PerlIO_get_bufsiz(PerlIO *);

In almost all platform stdio implementations, the C FILE struct has members
that may vary in name but serve the same purpose:  an internal buffer, and
some way (pointer or offset) to get at "the next" buffer character.  The
guys above are usually just (after layers & layers of config stuff sets it
up) macros that expand into the platform's internal way of spelling these
things.  For example, the count member is spelled under Windows as fp->_cnt
under VC, or as fp->level under Borland.

The payoff is in Perl's sv_gets function, in file sv.c.  This is long and
very complicated, but at its core has a fast inner loop that copies
characters (provided the PerlIO_has/canXXX functions say it's possible)
directly from the stdio buffer into a Perl string variable -- in the way a
platform fgets function *would* do it if it bothered to optimize fgets.  In
my experience, platforms usually settle for the same kind of
fgetc/EOF?/newline? loop Python uses, as if fgets were a stdio client rather
than a stdio primitive.  Perl's keeps everything in registers inside the
loop, updates the FILE struct members only at the boundaries, and doesn't
check for EOF except at the boundaries (so long as the buffer has unread
stuff in it, you can't be at EOF).

If the stdio buffer is exhausted before the input terminator is seen (Perl
has "input record separator" and "paragraph mode" gimmicks, so it's hairier
than just looking for \n), it calls PerlIO_getc once to force the platform
to refill the buffer, and goes back to the screaming loop.

Major hackery, but major payoff (on most platforms) too.  The abstract I/O
layer is a fine idea regardless.  The sad thing is that the real reason Perl
is so fast here is that platform fgets is so needlessly slow.

perl-input-is-faster-than-c-input-ly y'rs  - tim