asynchat sends data on async_chat.push and .push_with_producer

Josiah Carlson josiah.carlson at gmail.com
Tue May 13 21:29:16 EDT 2008


On May 13, 9:35 am, "ludvig.eric... at gmail.com"
<ludvig.eric... at gmail.com> wrote:
> > In a pure world, the only writing that is done would be within the
> > handle_send() callbacks within the select loop.  Then again, in a
> > perfect world, calling readable() and writable() would have no strange
> > side affects (as your example below has), and all push*() calls would
> > be made within the handle_*() methods.
>
> It wouldn't have those side-effects if push really just pushed. :-P
>
> > We do not live in a pure world, Python isn't pure (practicality beats
> > purity), and by attempting to send some data each time a .push*()
> > method is called, there are measurable increases in transfer rates.
>
> -- 8< --
>
> > Yes, it would be convenient to not have push*() actually send data
> > when called in some cases, but in others, the increase in data
> > transfer rates and/or reduction in latency is substantial.
>
> If it increases transfer speed that much, the calling application
> almost has to be broken, or at least not designed as it should be - of
> course there are such applications, but you know...

It's not a matter of being broken at all, it's a matter of control
flow.  When we immediately try to send whenever a .push() call is
made, the underlying TCP/IP stack will accept a reasonably large
amount of data before it actually fills up (the most recent FreeBSD,
from what I understand, will accept up to 1 meg, which is how they are
able to saturate 10Gbit links), and by tossing the data into the the
TCP/IP buffer early, the data gets sent earlier, thus reducing
latency.

Further, because we are making more actual calls to socket.send(),
assuming the underlying TCP/IP buffer isn't filled (which may or may
not be a good assumption), and assuming that the link has more
capacity than is being used (usually the case on LANs and high-speed
internet links), putting more data into the buffer to be handled by
the underlying link layers will also increase transfer speeds.

When the socket.send() calls are delayed until the next pass through
the loop, and we aren't doing an initial send, then we don't get the
benefit of the underlying TCP/IP socket layer buffering.

In my experience over high-speed connections (LANs, Gbit WAN links,
local machine connections), I have found that increasing block sizes
to 32k to significantly improve performance for bandwidth constrained
applications, as there are far fewer blocks to toss to the underlying
layers, less Python code execution (Python 2.5 has a default block
size of 512 bytes, or 64x as much Python execution to send the same
amount of data, and one of the proposed 2.6 changes is to up this to a
more reasonable 4096 bytes), and more effective use of the TCP/IP
buffers (which are typically 64k or larger).

> Anyway, I went for a subclassing way of dealing with it, and it works
> fine.
>
> Thanks for the reply though, hadn't considered possibly "flawed"
> applications where the asyncore loop isn't revisited as often as it
> should. :->
> Ludvig

Again, it's not about the application being flawed, it's a matter of
control flow. ;)  Also, it's not a matter of any timeouts in the
select/poll loops (as Giampaolo suggested); if any socket is readable
or writable, those calls will return immediately (a few hundred
microseconds per call isn't bad).

 - Josiah



More information about the Python-list mailing list