Magic Optimisation

ABO abo at google.com
Wed Sep 14 07:18:44 EDT 2005


Bengt Richter wrote:
> On 5 Sep 2005 07:27:41 -0700, "Paul McGuire" <ptmcg at austin.rr.com> wrote:
>
> >I still think there are savings to be had by looping inside the
> >try-except block, which avoids many setup/teardown exception handling
> >steps.  This is not so pretty in another way (repeated while on
> >check()), but I would be interested in your timings w.r.t. your current
[...]
> Why not let popleft trigger an exception out of while True instead,
> and prevent tasks from raising StopIteration, and let them yield a None
> to indicate keep scheduling with no special action, and something else
[...]

The rule of thumb with exceptions is use them for infrequent events. If
you keep to this you end up with clean and fast code.

Provided task.next() raises StopIteration less than about 25% of the
time it is called, it is cleaner and more efficient to use an exception
than to return None. It is also more efficient to handle terminating by
allowing popleft trigger an exception. Try the following;

def loop(self):
    self_call_exit_funcs = self.call_exit_funcs
    self_pool_popleft = self.pool.popleft
    self_pool_append = self.pool.append
    while True:
        try:
            task = self_pool_popleft()
            task.next()
            self_pool_append(task)
        except StopIteration:
            self_call_exit_funcs(task)
        except IndexError:
            break

There are other "optimisations" that could be applied that make this
code faster but uglier. For example, putting another "while True: loop
inside the try block to avoid the try block setup each iteration. Also,
exception handling is slower when you specify the exception class (it
has to check if the exception matches), so you might be able to arrange
this with an anonymous accept: block around the task.next() to handle
the StopIteration exception.

Another thing that disturbs me is the popfirst/append every iteration.
Depending on how many loops through all the tasks before one
"finishes", you might find it more efficient to do this;

def loop(self):
    self_pool = self.pool
    self_pool_remove = self_pool.remove
    self_call_exit_funcs = self.call_exit_funcs
    while self_pool:
        try:
            for task in self_pool:
                task.next()
        except:
            self_pool_remove(task)
            self_call_exit_funcs(task)




More information about the Python-list mailing list