Argonne National Laboratory

Experimental Physics and
Industrial Control System

1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  <20122013  2014  2015  2016  2017  2018  2019  Index 1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  <20122013  2014  2015  2016  2017  2018  2019 
<== Date ==> <== Thread ==>

Subject: RE: Invitation to test cothread.catools release candidate
From: <michael.abbott@diamond.ac.uk>
To: <newville@cars.uchicago.edu>
Cc: tech-talk@aps.anl.gov
Date: Tue, 13 Mar 2012 08:12:54 +0000
From: matt.newville@gmail.com [mailto:matt.newville@gmail.com] On
> Thanks for the reply.  You might have to bear through some more
> questions...
> 
> > 1. Controlled concurrency.
...
> >
> > With Python threads it seems to me that the biggest down side of
> > cothread, that there is only one thread of execution, has much
> > less impact, as Python only ever executes one thread at a time
> >  in the interpreter anyway!
> 
> To me, this would seem to have very little to do with Channel Access,
> and more to do with python's threading implementation.  Is that a fair
> characterization?  If so, should it be a separate python package,
> useful outside of the scope of Channel Access?

Certainly cothread stands alone.  The only reason it's so tightly bound to cothread.catools is that catools is implemented in an essential way on top of cothread and cothread doesn't currently have a separate existence.  In truth I see the absence of working Windows support as an Achille's heel here.

> > 2. Very light weight "threads"
> >
> > Coding with callbacks requires a continuation style of programming
> > mixed with threading based events when you need to resynchronise
> > with the original caller.  This is definitely harder to write,
> > particularly when exceptions and
> > error handling need to be taken into account.
> 
> I'm not sure I'm following you here.  Concrete examples would be nice.

Ok, I'll try.  I developed an example in draft yesterday but threw it away, but I think it's worth doing again.  I'll need to go into quite a bit of detail, but I think with your comments below on how to use pyepics and your caget() code in front of me for contrast I can make the necessary points.

Let's first look at a schematic of the implementation of caget() in cothread.catools:

	def caget(pvs, **kargs):
	    if isinstance(pvs, str):
	        return caget_one(pvs, **kargs)
	    else:
	        return caget_array(pvs, **kargs)

Actually, this isn't schematic at all, this is the full truth.  This "polymorphism" of caget() was very contentious when James and I were originally developing this library, but our experience with this design has been very good.  Now, straight away I can show my first essential use of cothreads:

	def caget_array(pvs, **kargs):
	    return cothread.WaitForAll([
	        cothread.Spawn(caget_one, pv, raise_on_wait=True, **kargs)
	        for pv in pvs])

Again this is full code.  The point here is that cothread.Spawn() launches concurrent copies of caget_one(pv, **kargs) for each requested pv in pvs and then cothread.WaitForAll() gathers all the concurrent results together.  The raise_on_wait flag ensures that when waiting for the spawned process to complete if it raised an exception then the waiter receives the exception (otherwise the exception is just logged and the waiter receives None).

I think I can already compare with true threads and with callbacks at this point.  We *can* use true threads here, but it's expensive and wasteful, and without careful management of stack sizes resource usage will rapidly explode.  Using continuations is a lot more awkward in this style.  I've not yet worked through a full working implementation of caget_array with continuations, but it is definitely less clear.

	def caget_one(pv, timeout=5):
	    channel = _channel_cache[pv]
	    channel.Wait(timeout)
	    done = cothread.Event()
	    cadef.ca_array_get_callback(
	        dbrcode, count, channel, _caget_event_handler, done)
	    return done.Wait(timeout)

Now this is ludicrously simplified, I've removed most of the optional arguments, the observant reader will be wondering where dbrcode and count came from, and I guess OMAR (I reference ONAG) will be worrying a little about timeout and the lifetime of done.

My point is that there are two blocking (or suspension) points in this code, the two .Wait() calls, where the inline code cannot proceed until something has happened elsewhere.  In the first case, we're waiting for channel connection to complete (if it has already connected, this will of course complete immediately without suspension), and in the second we're waiting for ca_array_get_callback to call back.

Of course with ordinary threads this is easy, though of course we have to pay two system calls per Wait.  With callbacks quite a rewrite is needed to achieve the same effect ... particularly if we think carefully about how we're going to handle timeouts and exceptions.

To finish off:

	@cadef.event_handler
	def _caget_event_handler(args):
	    if args.status == cadef.ECA_NORMAL:
	        args.usr.Signal(dbr_to_value(args))
	    else:
	        args.usr.SignalException(ca_nothing(pv, args.status))

Just to tidy things up.  Don't look too closely, this code is full of lies, however the call to SignalException() is a nice trick to forward a caget error through done event object where it is then raised as an exception to done.Wait().


I'm intrigued by Michael Davidsaver's link to implementing continuations with generators ... but if you think about it, generators are lightweight coroutines with language support.


So let me try to recap.  Cothread lets you write blocking operations *inline* which means that the resulting code is "composable": see how I've been able to build caget_array() from caget_one() without having to modify caget_one().  Continuation programming is harder because the flow of control is necessarily chopped into pieces between suspension points and state needs to be managed explicitly rather than being, if I may so put it, implicit in the program counter.


> I think I must still not be understanding you.  Certainly caget() on a
> list of a few hundred PVs is not challenging, and should be dominated
> by network i/o not anything to do with Python.  Are you comparing
> sequential cagets:
> 
>   for pvname in list_of_pvnames:
>       print caget(pvname)
> 
> with creating 1 thread per PV?  If so, I can certainly see why
> lightweight threads are an advantage.

No, of course I'm comparing parallel gets, as in

	print caget(list_of_pvnames)

> I think part of my confusion is that I can easily, and quickly, fetch
> hundreds of PVs without thinking about python threads at all, but
> using just straightforwand wrappings of the CA library.  My
> inclination is to believe that the easier approach is better until
> proven worse.  You've definitely put a lot of effort into cothread, so
> maybe there is something you're doing that is better.
> 
> Doing a simple comparison of cothread.catools.caget and pyepics.caget
> (which is definitely NOT optimized for speed when fetching many PV
> values, see http://pyepics.github.com/pyepics/advanced.html#strategies-
> for-connecting-to-a-large-number-of-pvs
> for details), shows that the speeds for  (with 220 PVs, all really
> connected and on the same subnet)
> 
>   for pvname in list_of_pvnames:
>       print cothread.catools.caget(pvname)
> 
>   for pvname in list_of_pvnames:
>       print epics.caget(pvname)
> 
> are essentially identical (at ~5.5 seconds each), so I suspect that
> this naive use of cothread.catools.caget is not really using
> cooperative threads.  In comparison,
>   pvs = []
>   for pvname in list_of_pvnames:
>       pvs.append(epics.PV(pvname))
>   for p in pvs:
>       x = p.get()
> 
> comes in at about 0.3 seconds.  So it's much, much faster to allow
> connection and automated monitoring callbacks to happen in the
> background of the CA library than to do sequential "create channel,
> connect channel, get".  Another improvement of ~3x (to 0.1 seconds)
> can be gained by avoiding the overhead of PVs, suppressing connection
> callbackes, and issuing get()'s with a callback, and then waiting for
> them all to complete in the background as described in the link above.
>   Sometimes, that extra performance is worth the extra effort, though
> I would say that for a couple hundred PVs, the improvement probably
> isn't needed.  GUI screens with lots of PVs show up just fine for me,
> for example.
> 
> But none of that uses Python threads at all, just documented use of
> the standard Channel Access library.  So, I'm still left wondering
> when cothreads offers a real advantage for using Channel Access.

-- 
This e-mail and any attachments may contain confidential, copyright and or privileged material, and are for the use of the intended addressee only. If you are not the intended addressee or an authorised recipient of the addressee please notify us of receipt by returning the e-mail and do not use, copy, retain, distribute or disclose the information in or attached to the e-mail.
Any opinions expressed within this e-mail are those of the individual and not necessarily of Diamond Light Source Ltd. 
Diamond Light Source Ltd. cannot guarantee that this e-mail or any attachments are free from viruses and we cannot accept liability for any damage which you may sustain as a result of software viruses which may be transmitted in or with the message.
Diamond Light Source Limited (company no. 4375679). Registered in England and Wales with its registered office at Diamond House, Harwell Science and Innovation Campus, Didcot, Oxfordshire, OX11 0DE, United Kingdom
 





Replies:
Re: Invitation to test cothread.catools release candidate Matt Newville
References:
Invitation to test cothread.catools release candidate michael.abbott
Re: Invitation to test cothread.catools release candidate Matt Newville
RE: Invitation to test cothread.catools release candidate michael.abbott
Re: Invitation to test cothread.catools release candidate Matt Newville

Navigate by Date:
Prev: RE: SmarAct / SmarPod Emma Shepherd
Next: RE: Invitation to test cothread.catools release candidate michael.abbott
Index: 1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  <20122013  2014  2015  2016  2017  2018  2019 
Navigate by Thread:
Prev: Re: Invitation to test cothread.catools release candidate Michael Davidsaver
Next: Re: Invitation to test cothread.catools release candidate Matt Newville
Index: 1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  <20122013  2014  2015  2016  2017  2018  2019 
ANJ, 18 Nov 2013 Valid HTML 4.01! · Home · News · About · Base · Modules · Extensions · Distributions · Download ·
· Search · EPICS V4 · IRMIS · Talk · Bugs · Documents · Links · Licensing ·