[Stackless] verify my performance numbers?

Andrew Dalke dalke at dalkescientific.com
Thu Feb 22 10:29:23 CET 2007


On Jan 30, 2007, at 3:47 PM, Santiago Gala wrote:
> No need for it being re-entrant, it works because both producer and
> consumer calls that use it are wrapped with a shared lock, i.e.,  
> marked
> as critical sections in a fairly standard way. Those are the
>
> with lock:
>     <code>
>
> calls in the producer and the consumer. Basically only one thread
> touches it at a time.
>
> The Queue class is supposed to do the same, but it looks, on a quick
> glance, overoptimized, which makes it slower. I could be wrong, I'm  
> not
> an expert pythoneer, but it looks like too much python code for  
> what it
> is supposed to do.

I took a look into your approach and included its timing numbers
on my PyCon talk.  I found that using Stackless was slightly
faster than using a thread+lock

    Normal Python, counter in handler:  1.46 s
    Stackless iterparser:  2.25 s
    threaded, lock around a deque:  2.34 s
    threaded, using Queue.Queue(1000): 13.1  s

Here's my final test code

from __future__ import with_statement
import xml.sax
import thread
import threading
import collections

class LockingIterHandler(xml.sax.ContentHandler):
     def __init__(self, lock, queue):
         self.lock = lock
         self.queue = queue
     def startElement(self, tag, attrs):
         with self.lock:
             self.queue.append( ("startElement", (tag, attrs)) )
     def endDocument(self):
         with self.lock:
             self.queue.append(None)

def iterparse(filename):
     queue = collections.deque()
     lock = thread.allocate_lock()
     handler = LockingIterHandler(lock, queue)
     parser = xml.sax.make_parser()
     parser.setContentHandler(handler)
     threading.Thread(target=parser.parse, args=(filename,)).start()
     while 1:
         if queue:
             with lock:
                 while queue:
                     data = queue.popleft()
                     if data is None:
                         return
                     yield data
         time.sleep(0.001)

import time
t1=time.time()
count = 0
for event, args in iterparse("iTunes Music Library.xml"):
     if event == "startElement":
         count += 1
t2=time.time()
print count, "elements"

print t2-t1


With some experimentation I found the reason why it's
successful is that the producer is much faster than
the consumer.  The queue gets a lot of items on it
which are then processed in one lump.

That amortizes the function call overhead of all
of the with statement calls.

For example, if I replace

             with lock:
                 while queue:
                     data = queue.popleft()
                     if data is None:
                         return
                     yield data

with something which consumes 1 item at a time in the lock
rather than a block of items

             with lock:
                 data =  queue.popleft()
                 if data is None:
                     return
                 yield data

then the time lengthens to (as I recall) about 20 seconds.

So for this example (converting XML SAX processing
into an iterator) it works, but the approach is not
a general purpose solution.

Also, it assumes the deque may be unbounded, while
some may want at most some fixed N read-aheads.  That
would slow down processing a bit, though I don't
think it would be much.


                                 Andrew
                                 dalke at dalkescientific.com



_______________________________________________
Stackless mailing list
Stackless at stackless.com
http://www.stackless.com/mailman/listinfo/stackless



More information about the Stackless mailing list