[Date Prev] [Date Next] [Thread Prev] [Thread Next] Date Index Thread Index Search archive:
Date:Mon Nov 22 13:14:43 1992 
Subject:Re: Clarity VS Efficiency in POP11 
From:Ian Rogers 
Volume-ID:921122.04 

tmr@cs.bham.ac.uk (Tim Read) writes:
> It is my feeling as a competent beginner in POP11, that putting too many
> efficiency hacks into your code tends to lose the inherent clarity that
> POP11 provides.

Efficiency *hacks* are bad, sure (Pop11 buys you clarity, a Good
Thing IMHO)

> My conclusion is that (bearing in mind I am using an 86 MIP workstation), I
> will not worry about efficiency until I notice that my program is running
> slowly.
> ....
> Until I notice that it takes longer for the program to run than it does for
> the data to be output, I'll lose no sleep over it....

But it's this sort of attitude that's brought us dogs like
OpenWindows (I now need a SuperZoom Mega BitBlitter graphics card
with dual carbs, turbo charger and nitrous' laser injection paint
whacker before my xterm can come up within a week :-(

> An example of what I mean is using a temporary closure within a
> loop, which looks nice and neat, but will cause extra garbage collections,
> as opposed to using an lblock (As Aaron pointed out).

So make a new data type (even notionally) and optimise the code,
*after* you've finished development, by free-listing instances.

A case in point:

Last week I was developing a graphics package to draw links between
graph nodes. The data structure for the lines are shortvecs of
between 8 and 12 elements. These were being thrown around willy
nilly, but that's ok 'cos I've got a garbage collector.

So I set up an animation to demonstrate the links and I keep getting
1 second pauses. Hmm, garbage collector, bad news in direct
manipulation user interfaces.

So I write a free-listing utility:

    lconstant
            pvh_len = 12,
            pv_hold = initv(pvh_len),
        ;

    define free_pointvec(v);
        lvars   v, i = length(v),
            ;
        returnif(i fi_> pvh_len);
        /***
            we need the second -fast_subscrv- to "persuade" the
            shortvec to act as a full vector
         ***/
        fast_subscrv(i, pv_hold) -> fast_subscrv(1, v);
        v -> fast_subscrv(i, pv_hold);
    enddefine;

    define make_pointvec(n);
        lvars   n, v,
            ;
        if n fi_> pvh_len or (fast_subscrv(n, pv_hold) ->> v) == 0 then
            consshortvec(n)
        else
            fast_subscrv(1, v) -> fast_subscrv(n, pv_hold);
            () -> explode(v);
            v
        endif
    enddefine;

    constant old_pop_after_gc = pop_after_gc;

    /***
        The free-list table will be completely corrupted after a GC
        so we'd better clear it out :-)
     ***/
    define clear_pv_hold;
        set_subvector(0, 1, pv_hold, pvh_len);
        old_pop_after_gc();
    enddefine;

    define vars procedure pop_after_gc =
        clear_pv_hold;
    enddefine;

Then I change all relevant calls to -consshortvec- with
-make_pointvec-. Easy peasy, and the semantics and the look of the
main body of the code hasn't changed one jot.

The optimistations then come in carefully selecting where to place
calls to -free_pointvec-. Purely by coincidence, I'm programming in
the OOP paradigm so no other piece of code gets a look in on these
data structures. There's only one place where the vectors are being
trashed before making a new one. So:

    if link.pvec then
        free_pointvec(link.pvec);
        false -> link.pvec; ;;; just to make sure I don't try to use
                            ;;; it later
    endif;

This has now completely changed the store usage of my link objects.
In the worst case, a link object will generate one of each size of
vector and then cycle them through the free list.

No more pauses in my user interface, Yipeee!! :->

I've just realised I haven't answered your implied question. Hmm...

In your case you can free-list the closures, either in a simple list
if you're only making closures of a single procedure, or in a
property that matches against the pdpart. I'll tackle the difficult
case (if the weather wasn't so bad I'd be out walking on the South
Downs :( )

    define lconstant pdr_table =
        newproperty([], 8, [], "tmpboth")
    enddefine;

    define make_closure(n) -> clos;
        lvars   n, i, args, free, clos, pdr,
            ;
        conslist(n) -> args;    ;;; get the rest of the stuff
        () -> pdr;              ;;; from the stack
        pdr_table(pdr) -> free;
        if free == [] then
            ;;; just like before
            partapply(pdr, args) -> clos;
        else
            destpair(free) -> pdr_table(pdr) -> clos;
            1 -> n;
            for i in args do
                i -> frozval(n, clos);
                n + 1 -> n;
            endfor;
        endif;
        sys_grbg_list(args);
    enddefine;

    define free_closure(clos);
        lvars   clos, pdr = pdpart(clos),
            ;
        clos :: pdr_table(pdr) -> pdr_table(pdr);
    enddefine;

    vars a = identfn(% "a", "b" %);
    frozval(1, a) =>
    ** a

    vars b = make_closure(identfn, #| "a", "b" |#);
    frozval(1, b) =>
    ** a

    free_closure(a);
    vars c = make_closure(identfn, #| "c", "b" |#);
    frozval(1, c) =>
    ** c

Ian.
             "Deep in the fundamental heart of mind and Universe,"
           said Slartibartfast, "there is a reason" - Douglas Adams