Nicholas Piël

  • Home
  • About
  • Projects

Asynchronous Servers in Python

Nicholas Piël | December 22, 2009

There has already been written a lot on the C10K problem and it is known that the only viable option to handle LOTS of concurrent connections is to handle them asynchronously. This also shows that for massively concurrent problems, such as lots of parallel comet connections, the GIL in Python is a non-issue as we handle the concurrent connections in a single thread.

In this post i am going to look at a selection of asynchronous servers implemented in Python.

Asynchronous Server Specs

Since Python is really rich with (asynchronous) frameworks, I collected a few and looked at the following features:

  • What License does the framework have?
  • Does it provide documentation?
  • Does the documentation contain examples?
  • Is it used in production somewhere?
  • Does it have some sort of community (mailinglist, irc, etc..)?
  • Is there any recent activity?
  • Does it have a blog (from the owner)?
  • Does it have a twitter account?
  • Where can i find the repository?
  • Does it have a Thread Pool?
  • Does it provide access to a TCP Socket?
  • Does it have any Comet features?
  • Is it using EPOLL?
  • What kind of server is it? (greenlets, callbacks, generators etc..)

This gave me the following table.

NameLic.DocEx.Prod.Com.Act.BlogTwtRep.PoolWsgiScketCmetEpollTestStyle
TwistedMITYesYesYesHugeYesLotsNoTracYesYesYesNoYesYesCallback
TornadoApacheYesYesF.FeedYesYesFBYesGHubNoLim. YesNoYesNoAsync
OrbitedMITYesYesYesYesYesYesNoTracNoNoYesYesYesYesCallback
DieselWebBSDYesYesSTalkYesYes YesYesBitB.NoLim.YesYesYesNoGenerator
MultiTaskMITSomeNoNoNoNoYesNoBzrNoNoNoNoNoNoGenerator
ChiralGPL2APINoNoIRCNoNoNoTracNoYesYesYesYesYes Coroutine
EventletMITYesYesS. Life YesYesYesNoBitB.YesYes YesNoYesYesGreenlet
FriendlyFlowGPL2Some OneNoNoNoNoYesGgleNoNoYesNoNoYesGenerator
WeightlessGPL2YesNoYesNoNoNoYesSFNoNoYesNoNoYesGenerator
FibraMITNoNoNoNoNoYesNoGgleNoNoYesNoNoNoGenerator
ConcurrenceMITYesYeshyvesYesYesNoNoGHubNoYesYesNoYesYes Tasklet
CircuitsMITYesYesYesYesYesYesYesTracNo YesYesNoNoYesAsync
GeventMITYesYesYesYesYesYesYesBitB.NoYesYesNoYesYesGreenlet
CogenMITYesYesYesNoYesYesYesGgleNoYesYesNoYesYesGenerator

This is quite a list and i probably still missed a few. The main reasons for using a framework and not implementing something your self is that you hope to be able to accelerate your own development process by standing on the shoulders of other developers. I think it therefore is important that there is documentation, some sort of developers community (mailinglist fe)  and that it is still active. If we take this as a requirement we are left with the following solutions:

  • Orbited / Twisted (callbacks)
  • Tornado (async)
  • Dieselweb (generator)
  • Eventlet (greenlet)
  • Concurrence (stackless)
  • Circuits (async)
  • Gevent (greenlet)
  • Cogen (generator)

To quickly summarize this list; Twisted has been the de-facto standard to async programming with Python. It has an immense community, a wealth of tools, protocols and features. It has grown big and some say it reminds them of shirtless men drinking Jager-bombs complex. This is also one of the biggest reasons why people are looking elsewhere. Recently Facebook released the code of their async. approach called Tornado which is also using callbacks and recent benchmark show that it outperforms Twisted.

A common heard argument against programming with callbacks is that it can get overly complex. A programmatically cleaner approach is to use light-weight threads (imho). This can be achieved by using a different Python implementation; Stackless (such as Concurrence is using) or a plugin for regular python Greenlet (such as Eventlet and Gevent are using). Another approach is to simulate these light-weight threads with Python generators, such as Dieselweb and Cogen are doing.

This should already show that while all these frameworks provide you asynchronous concurrency they do this in each of their own ways. I want to invite you to look at these frameworks as they all have their own code gems. For example, Concurrence has a non-blocking interface to MySQL. Eventlet has a neat thread-pool, Tornado can pre-fork over CPU’s, Gevent offloads HTTP header parsing and DNS lookups to Libevent, Cogen has sendfile support and Twisted probably already has a factory doing exactly what you are planning to do next.

The Ping Pong Benchmark

biba_golic_11In this benchmark i am going to focus on the performance of the framework to listen on a socket and write to incoming connections. The client pings the socket by opening it, the server responds with a ‘Pong!’ and closes the socket. This should be really simple but it is a pain to create something that does this in an asynchronous and non-blocking way from scratch and that is exactly the reason why we are looking at these frameworks. It is all about making our lives easier.

Ok, for this benchmark i am going to use httperf,  a high performance tool that understands the HTTP protocol. If we want httperf to play along in our Ping-Pong benchmark we have to make it understand the ‘PONG!’ response. We can do this by mimicking a HTTP server and have our server respond with:

HTTP/1.0 200 OK
Content-Length: 5

Pong!

instead of just ‘Pong!’. Also, since most default server configurations are not set up to handle a large amount of concurrent requests, we need to make a few adjustments:

  • Raise the per-process file limit by compiling httperf after some adjustments.
  • Raise the per-user file limit, set ‘ulimit -n 10000‘ on both server and client.
  • Raise kernel limit on file handles: ‘echo “128000″ > /proc/sys/fs/file-max’.
  • Increase the connection backlog, ‘sysctl -w net.core.netdev_max_backlog = 2500‘
  • Raise the maximum connections with ’sysctl -w net.core.somaxconn = 250000‘

With these settings my Debian Lenny system was ready to hammer the different servers up to rates far beyond the capacity of the frameworks. I used the following command

httperf –hog –timeout=60 –client=0/1 –server=localhost –port=10000 –uri=/ –rate=400 –send-buffer=4096 –recv-buffer=16384 –num-conns=40000 –num-calls=1

And increased the rate with an interval of 100 from 400 up to 9000 requests per second for a total of 40.000 requests at each interval.

Code

What will now follow, is the implementation of the server side in the different frameworks. It should show the different approaches the frameworks take.

Twisted

Gentlemen start your reactor!

from twisted.internet import epollreactor epollreactor.install()
from twisted.internet.protocol import Protocol, Factory
from twisted.internet import reactor

class Pong(Protocol):
 def connectionMade(self):
 self.transport.write("HTTP/1.0 200 OK\r\nContent-Length: 5\r\n\r\nPong!\r\n")
 self.transport.loseConnection()

# Start the reactor
factory = Factory()
factory.protocol = Pong
reactor.listenTCP(8000, factory)
reactor.run()

Tornado

Tornado, does not hide the raw socket interface, which makes this example more lengthy then the others.


import errno
import functools
import socket
from tornado import ioloop, iostream

def connection_ready(sock, fd, events):
    while True:
        try:
            connection, address = sock.accept()
        except socket.error, e:
            if e[0] not in (errno.EWOULDBLOCK, errno.EAGAIN):
                raise
            return
        connection.setblocking(0)
        stream = iostream.IOStream(connection)
        stream.write("HTTP/1.0 200 OK\r\nContent-Length: 5\r\n\r\nPong!\r\n", stream.close)

if __name__ == '__main__':
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.setblocking(0)
    sock.bind(("", 8010))
    sock.listen(5000)

    io_loop = ioloop.IOLoop.instance()
    callback = functools.partial(connection_ready, sock)
    io_loop.add_handler(sock.fileno(), callback, io_loop.READ)
    try:
        io_loop.start()
    except KeyboardInterrupt:
        io_loop.stop()
        print "exited cleanly"

Dieselweb

While this example is beautifully small, i do not really enjoy the generator approach which sprinkles ‘yield’ all over the place.

from diesel import Application, Service

def server_pong(addr):
    yield "HTTP/1.0 200 OK\r\nContent-Length: 5\r\n\r\nPong!\r\n"

app = Application()
app.add_service(Service(server_pong, 8020))
app.run()

Circuits

I think the Circuit code is the most beautiful of them all, very elegent.

from circuits.net.sockets import TCPServer

class PongServer(TCPServer):

    def connect(self, sock, host, port):
        self.write(sock, 'HTTP/1.0 200 OK\r\nContent-Length: 5\r\n\r\nPong!\r\n')
        self.close(sock)

PongServer(('localhost', 8050)).run()

Eventlet

The Eventlet uses a Greenlet approach.

from eventlet import api

def handle_socket(sock):
    sock.makefile('w').write("HTTP/1.0 200 OK\r\nContent-Length: 5\r\n\r\nPong!\r\n")
    sock.close()

server = api.tcp_listener(('localhost', 8030))
while True:
    try:
        new_sock, address = server.accept()
    except KeyboardInterrupt:
        break
    # handle every new connection with a new coroutine
    api.spawn(handle_socket, new_sock)

Gevent

Gevent is presented as a rewrite of eventlet focussing on performance.

import gevent
from gevent import socket

def handle_socket(sock):
    sock.sendall("HTTP/1.0 200 OK\r\nContent-Length: 5\r\n\r\nPong!\r\n")
    sock.close()

server = socket.socket()
server.bind(('localhost', 8070))
server.listen(500)
while True:
    try:
        new_sock, address = server.accept()
    except KeyboardInterrupt:
        break
    # handle every new connection with a new coroutine
    gevent.spawn(handle_socket, new_sock)

Concurrence

Concurrence uses the Tasklet approach, it can be run under Greenlet and under Stackless Python. In this benchmark there was not really any performance difference between the two different engines.

from concurrence import dispatch, Tasklet
from concurrence.io import BufferedStream, Socket

def handler(client_socket):
    stream = BufferedStream(client_socket)
    writer = stream.writer
    writer.write_bytes("HTTP/1.0 200 OK\r\nContent-Length: 5\r\n\r\nPong!\r\n")
    writer.flush()
    stream.close()

def server():
    server_socket = Socket.new()
    server_socket.bind(('localhost', 8040))
    server_socket.listen()

    while True:
        client_socket = server_socket.accept()
        Tasklet.new(handler)(client_socket)

if __name__ == '__main__':
    dispatch(server)

Cogen

Cogen, uses the generator approach as well.

import sys

from cogen.core import sockets
from cogen.core import schedulers
from cogen.core.coroutines import coroutine

@coroutine
def server():
    srv = sockets.Socket()
    adr = ('0.0.0.0', len(sys.argv)>1 and int(sys.argv[1]) or 1200)
    srv.bind(adr)
    srv.listen(500)
    while 1:
        conn, addr = yield srv.accept()
        fh = conn.makefile()
        yield fh.write("HTTP/1.0 200 OK\r\nContent-Length: 12\r\n\r\nHello World!\r\n")
        yield fh.flush()
        conn.close()

m = schedulers.Scheduler()
m.add(server)
m.run()

Results

The first graph clearly shows at which connection rate (on the horizontal axis) the successful connection rate starts to degrade. It shows a huge difference between the best performer; Tornado with 7400 requests per second and the worst, Circuits with 1400 requests per second (which doesn’t use EPOLL). This connection rate was sustained for at least 40.000 requests. We can see that, when the hammering of the server continues beyond rates the server can handle, the performance drops. This is caused by connection errors or timeouts.

This graph shows the response time, it is clearly visible that once the maximum connection rate has been reached the overal response time starts to increase.

The last graph shows the amount of errors, ie no return of a 200 detected by httperf. We can see a correlation between the performance of the server and the returned errors at a given request rate. The performing servers return less overall errors. There is however, one exception. Cogen was able to return ALL its requests successfully no matter how hard it was hammered. It is therefore not visible in this graph. This is interesting, at 9000 requests per second it was still able to answer all requests. However, the average connection time (from socket open till socket close) was about 7 seconds meaning that Cogen was serving about 28000 concurrent connections somewhat at reduced performance but not dropping them.

Notes

This post should make it clear that Python has a rich set of options toward asynchronous programming. All tested frameworks show great performance. I mean, even Circuits results with 1300 requests per second isn’t too bad. Tornado really blew me away with its performance at 7400 requests per second. But if i had to choose a favorite i would  probably go with Gevent, i am really digging its greenlet style.

The clean Greentlet / Stackless style is really cool, especially since Stackless Python is keeping up nowadays with CPython. There was some talk on a mailing list about Gevent running on Stackless. The concurrence framework already runs on Stackless and can thus be a great option already if you are looking for specific features of Stackless Python such as tasklet-pickling.

I want to make clear that this test only shows  how these frameworks perform at a relatively simple task. It could be that when more stuff is going on in the background the results will change. However, I feel that this benchmark is a great indicator of how each frameworks handles a socket connection.

In the coming days I plan to investigate this some more. I will also check out  how these Python frameworks stack up against its equivalents in different languages, fe Ape, CometD, NodeJS. Stay tuned!

Tags
async, comet, performance, programming, Python
RSS comments feed

« Person Recognition (with Python) Benchmark of Python WSGI Servers »

77 Responses to “Asynchronous Servers in Python”

  1. uberVU - social comments says:
    December 22, 2009 at 11:48 am

    Social comments and analytics for this post…

    This post was mentioned on Twitter by Nichol4s: New post: Asynchronous Servers in Python http://bit.ly/8UHKhK #in…

    Reply
    • Daily Python says:
      December 23, 2009 at 10:35 pm

      nice job on this… Circuits implementation looks really clean, but look like you pay for the simplicity in performance.

      Reply
  2. Vadim S. says:
    December 22, 2009 at 11:58 am

    Really nice benchmarks! Thanks for you work. It is very valuable.

    Reply
  3. Lenni says:
    December 22, 2009 at 12:09 pm

    Nice comparison!

    I however found it difficult to read the graphs and tell which line corresponds to which framework. The indicators in the legend are too small.

    Reply
    • Nicholas Piël says:
      December 22, 2009 at 12:11 pm

      When you hover with your mouse over the lines or the legend the javascript gods will tell you what is what.

      Cheers!

      Reply
  4. Henk Punt says:
    December 22, 2009 at 12:14 pm

    Congrats, great benchmark!,

    Some remarks (me being the author of Concurrence :-) …

    You list the Concurrence license as ‘Hyves’, but I think it should be MIT (at least that is the intention…, why did you think otherwise?).

    Also in your matrix you did not put an entry for ‘automated tests’. I think that should also count when considering frameworks, e.g. how mature is their developement process. For instance Twisted has a very large and comprehensive test-suite, and this is also something I strive for with Concurrence.

    It would also be nice if you mentioned memory usage for the various frameworks when having many connections at the same time. The stable version of Concurrence you tested for instance has a problem with using more memory than needed (current trunk version has that fixed).

    Reply
    • Nicholas Piël says:
      December 22, 2009 at 12:30 pm

      Hi Henk,

      Thanks for the remarks and opening up concurrence! I was not able to find any known license on the concurrence website or the repo. I only found this License on Github. As I am not that versed in the license-business I just named it Hyves to be sure. But since you say it is supposed to be MIT, i will update the matrix. Btw, this matrix does have a column ‘test’. But I agree that this doesn’t really say much.

      Benchmarking a lot of different frameworks is hard and i cut some corners here and there. Monitoring memory usage is one of them, maybe i’ll do this in the upcoming cross-language benchmark.

      Reply
    • Brian Hammond says:
      January 12, 2010 at 12:56 am

      Henk, I’m checking out Concurrence now. I like that the buffer management per client is clearly coded. I read the commit history and saw “buffer sharing” reduces memory. What does that really mean though?

      Reply
  5. Francisco Ribeiro says:
    December 22, 2009 at 12:57 pm

    Thank you, great post but… where is Kamaelia ?
    http://www.kamaelia.org

    Best Regards

    Reply
    • Nicholas Piël says:
      December 22, 2009 at 1:06 pm

      Damn,

      Yes that is one I can seriously not leave out. Will add that.

      Sorry about that.

      Reply
  6. Nicholas Piël » Socket Benchmark of Asynchronous Servers in Python « Netcrema – creme de la social news via digg + delicious + stumpleupon + reddit says:
    December 22, 2009 at 1:41 pm

    [...] Nicholas Piël » Socket Benchmark of Asynchronous Servers in Pythonnichol.as [...]

    Reply
  7. dh1r says:
    December 22, 2009 at 1:48 pm

    event driven != asynch*

    nice graphs.

    *posix

    Reply
  8. === popurls.com === popular today says:
    December 22, 2009 at 2:40 pm

    === popurls.com === popular today…

    yeah! this story has entered the popular today section on popurls.com…

    Reply
  9. Erik Gorset says:
    December 22, 2009 at 2:54 pm

    Event driven programming with greenlet is great. I’ve written about how to build a webserver using python’s BaseHTTPServer and coroutines at my blog.

    http://erik.gorset.no/2009/12/building-comet-enabled-http-server-in.html

    Reply
  10. testibus says:
    December 22, 2009 at 4:17 pm

    thanks, great work!

    Reply
  11. Greg says:
    December 22, 2009 at 5:23 pm

    Was surprised to see Tornado do so well.

    Reply
  12. a says:
    December 22, 2009 at 5:23 pm

    What about the good old asyncore based medusa?
    http://svn.zope.org/Zope/trunk/src/ZServer/medusa/

    Reply
    • Nicholas Piël says:
      December 23, 2009 at 11:41 am

      When i find some time i will add Kamaelia and Medusa to this benchmark.

      Have to get some xmas presents first ;)

      Reply
  13. links for 2009-12-22 at DeStructUred Blog says:
    December 23, 2009 at 3:03 am

    [...] Nicholas Piël » Socket Benchmark of Asynchronous Servers in Python (tags: python concurrency benchmark server network webdev library blog) [...]

    Reply
  14. Donovan Preston says:
    December 23, 2009 at 4:41 am

    Absolutely excellent post. It’s extremely gratifying to see so much interest in async/coroutine network servers. Since I started using Twisted in 2000 there has been little understanding or acceptance of this technique, so it’s extremely gratifying to see all the interest it has been getting lately.

    I agree that benching medusa and kamaelia would be excellent.

    One little nit, you did not specify the listen backlog parameter in either the gevent or eventlet cases. Specifying a large backlog might help reduce the error rate.

    Reply
    • Nicholas Piël says:
      December 23, 2009 at 11:39 am

      You are correct, i raised the backlog for all frameworks to 500 (well beyond what a normal linux distro allows; 128).

      Reply
  15. Michael Fischer says:
    December 23, 2009 at 6:13 am

    You didn’t post your server specs. How much RAM, how many CPUs (and what speed), and how much memory? Please also post the memory consumption (RSS/VSZ) of each server.

    Also, it would have been useful to rate for each module the quality of documentation (Twisted’s, for example, exists but is abysmal), whether it supports generic TCP connections easily, and whether it supports SSL and peer certificate authentication.

    Reply
    • Nicholas Piël says:
      December 23, 2009 at 12:00 pm

      The benchmark was run on a pretty old MacBook Pro 2.2Ghz core duo with 4 gigs of ram running Debian Lenny.

      I will look at the memory consumption in the upcoming cross-language benchmark.

      Concerning your other ‘requests’, a qualitative comparison of documentation is really hard, I have been focussing mainly on the framework acting as some sort of Comet / Websocket server and in that situation I don’t think SSL support is an issue as i imagine that in a production setting these daemons would be sitting behind a proxy anyway.

      Reply
      • Michael Fischer says:
        December 23, 2009 at 10:53 pm

        The reason I asked about generic TCP socket handling is that the title of your post is “Socket Benchmark of Asychronous Servers in Python,” not “Socket Benchmark of Asychronous HTTP Servers in Python.” There is a significant difference between the two.

        Also, SMP scalability is very important, which I why I asked about your server specs. Knowledge of a rate on 1 CPU is interesting (assuming there is no latency in request processing), but not very interesting if it doesn’t scale somewhat linearly with the number of CPUs in the system.

        SSL is important, for Comet or otherwise. Proxies obscure remote IP addresses and are best avoided if you care about DOS attack mitigation.

        Reply
  16. Jamie Kirkpatrick says:
    December 23, 2009 at 9:19 am

    Hey there

    Great post: I linked to it from my related question on stackoverflow (http://stackoverflow.com/questions/1824418/a-clean-lightweight-alternative-to-pythons-twisted) as I thought it might be useful for anyone landing there (seems to be a popular one from the number of votes).

    One point to note is that some of these frameworks are not fully portable. I looked at a lot of them when trying to decide what to use as a replacement for tornado (not portable) and found this to be an issue. It might be worth adding an extra column to the table at the top.

    Thanks for taking the time to do these tests though.

    Jamie

    Reply
    • Nicholas Piël says:
      December 23, 2009 at 11:45 am

      Yes, well i am mainly interested in Linux (and thus Epoll) but i might add that to this or the upcoming cross-language benchmark.

      In this case all frameworks that use Libevent (Gevent fe) should work on Windows.

      Thanks for adding a link from stackoverflow.com

      Reply
  17. Thomas Hervé says:
    December 23, 2009 at 11:29 am

    Making sure the backlog is the same everywhere would be nice. For example, you’re specifying 5000 in the Tornado code, whereas the Twisted default value is 50. Passing backlog=5000 to reactor.listenTCP would fix that.

    Reply
    • Nicholas Piël says:
      December 23, 2009 at 11:35 am

      Thomas, i already noticed that the backlog for twisted was 50 so i manually adjusted that.

      Did not know i could pass the backlog argument to listenTCP, thanks.

      Reply
  18. Sergey Shepelev says:
    December 23, 2009 at 12:59 pm

    Excellent!

    Good to see Twisted drop down.

    Sad to see eventlet showing more errors than gevent.

    Reply
  19. slav0nic says:
    December 23, 2009 at 1:18 pm

    what about basic asyncore ? )

    Reply
  20. Ryan Williams says:
    December 23, 2009 at 5:17 pm

    Great roundup! The Eventlet benchmark could benefit from a little tweaking. I used the following code and achieved significantly better performance.

    from eventlet.green import socket
    from eventlet import api,hubs
    hubs.use_hub("pyevent")
    
    def handle_socket(sock):
        sock.sendall("HTTP/1.0 200 OK\r\nContent-Length: 5\r\n\r\nPong!\r\n")
        sock.close()
    
    server = socket.socket()
    server.bind(('localhost', 8030))
    server.listen(500)
    while True:
        try:
            new_sock, address = server.accept()
        except KeyboardInterrupt:
            break
        # handle every new connection with a new coroutine
        api.spawn(handle_socket, new_sock)
    

    The major difference here is that makefile() and its consequent object creation cost are not called. This alone makes a big difference, and has the side benefit of being more readable and closer in spirit to the other tests.

    Also, this code uses pyevent for event dispatching. Pyevent is disabled by default within Eventlet because it is not compatible with threads, but in this sort of test, it provides better performance.

    Reply
    • Ryan Williams says:
      December 23, 2009 at 5:27 pm

      Oh wow that is bad formatting. Sorry, I hope you get the idea. Thanks again for doing this great roundup.

      Reply
      • Nicholas Piël says:
        December 23, 2009 at 5:31 pm

        I fixed the markup, you can enclose code between

        [ + py + ] …code here… [ + /py + ]

        tags, at least on this blog. However, most other Wordpress blogs support the [/code] tag, just FYI. I will try the sock.sendall approach + py events somewhere after xmas.

        Thanks for your suggestion!

        Reply
        • Ryan Williams says:
          December 23, 2009 at 8:44 pm

          Looking forward to the update!

          Reply
  21. links for 2009-12-23 « Donghai Ma says:
    December 24, 2009 at 5:04 am

    [...] Nicholas Piël » Socket Benchmark of Asynchronous Servers in Python (tags: python asynchronous benchmark concurrency server performance network twisted) [...]

    Reply
  22. Denis says:
    December 24, 2009 at 6:06 am

    I’d like to point out that gevent’s wsgi server is build on top of libevent-http module. As such it should be more efficient than most pure Python alternatives.

    Reply
  23. Daily Digest for December 24th | Reading Muzaki says:
    December 24, 2009 at 10:36 am

    [...] Benchmark of Asynchronous Servers in Python [...]

    Reply
  24. Amir says:
    December 24, 2009 at 12:31 pm

    Great post, thanks a lot! Also looking forward to any updates or followups.

    By the way, Chiral is GPLv2:
    http://chiral.j4cbo.com/trac/changeset?new=51%40%2F&old=50%40%2F

    P.S. it would also be appreciated if anybody who follows up with a comparison across languages includes Erlang’s OTP as well.

    Reply
  25. SMiGL says:
    December 24, 2009 at 9:09 pm

    Good post! Thanks

    Reply
  26. Amir says:
    December 24, 2009 at 9:39 pm

    Actually, it appears as though Circuits does have epoll support. You only need to provide the right class from circuits.net.pollers as a kwarg to circuits.net.Server():
    http://trac.softcircuit.com.au/circuits/browser/circuits/net/sockets.py#L394

    Reply
    • Nicholas Piël says:
      December 27, 2009 at 1:38 pm

      Thanks for the info!

      Reply
  27. Ionel Maries says:
    December 25, 2009 at 12:06 am

    I feel this is largely misinforming to someone who doesn’t understand how the frameworks work as the approaches and server architectures wildly differ:

    For example, tornado would fork in two processes and that’s the only reason it won the benchmark. It’s a nice feature but one could argue that this should be handled elsewhere (loadbalancer/clustering).

    gevent is largely written in C (well, pyrex apparently) and that’s the reason it’s the second fastest (it would clearly win if tornado would run in single process mode).

    Also, the server code approaches differ, some start coroutines/greenlets/whateverlets on a new connection and some don’t. This is a very important difference and the results can differ much depending on this aspect.

    The error rates worry me – I think it’s a good exercise to find out why they happen.
    I would add portability and test code coverage to the comparison table.

    Reply
    • Nicholas Piël says:
      December 27, 2009 at 1:37 pm

      The benchmarked tornado does NOT prefork and thus uses only one process. This functionality has only been added very recently to the trunk.

      I don’t really think it is an issue how a certain framework has been implemented. The end user only cares about the ease of use and its performance. Not wether the heavy lifting is being done by an external library (such as libevent) or an optimized inner loop.

      I agree with you that there is some inconsistency with the functionality of the different implementations. A more thorough test could make those irrelevant. Ie, instead of a single ping-pong make the server respond to multiple ‘ping!’ requests by a single client, each fired with a certain interval.

      Reply
      • Ionel Maries says:
        December 28, 2009 at 5:19 pm

        True that.

        However, I noticed that you use smaller backlog and response for the cogen sample. Also, if you spawn a coroutine for each new connection and avoid makefile you can get a bit more response rate out of it. Eg:

        import sys
        from cogen.core import sockets
        from cogen.core import schedulers
        from cogen.core.coroutines import coroutine

        @coroutine
        def server():
        srv = sockets.Socket()
        adr = (‘0.0.0.0′, len(sys.argv)>1 and int(sys.argv[1]) or 1200)
        srv.bind(adr)
        srv.listen(5000)
        while 1:
        conn, addr = yield srv.accept()
        m.add(handler, args=(conn,))

        @coroutine
        def handler(conn):
        yield conn.send(“HTTP/1.0 200 OK\r\nContent-Length: 5\r\n\r\nPong!\r\n”)
        conn.close()

        m = schedulers.Scheduler()
        m.add(server)
        m.run()

        Reply
  28. Landreville says:
    December 28, 2009 at 5:26 pm

    Would using asynchronous code in twisted make a difference. I just notice that in diesel you are using non-blocking code by using the generator syntax where in the Twisted code you are using blocking code instead of returning a deferred or using a generator. Does this make a difference in the tests?

    Just to note, twisted also can use the generator syntax.

    Reply
    • Denis says:
      December 29, 2009 at 6:12 am

      Twisted example is asynchronous.

      transport.write() call merely buffers the data. Actual sending happens when the descriptor is ready for writing.

      Reply
  29. Brian Hammond says:
    January 11, 2010 at 10:18 pm

    I tried Circuits (on a Linode 360 running Linux 2.6) because its component architecture looked interesting and the example code was clean.

    $ sysctl net.core.somaxconn net.core.netdev_max_backlog
    net.core.somaxconn = 250000
    net.core.netdev_max_backlog = 2500

    $ cat /proc/sys/fs/file-max
    1001000

    $ ulimit -n
    1001000

    I changed port to 10000 in Circuits example code to match your httperf command-line.

    I also followed the instructions for installing a custom httperf. Be careful to use the proper httperf if you already had it installed (e.g. by default it installs to /usr/local/bin/).

    I also made sure that Circuits was using epoll:

    PongServer((‘localhost’, 10000), poller=EPoll, backlog=500).run()

    $ httperf –hog –timeout=60 –client=0/1 –server=localhost –port=10000 –uri=/ –rate=400 –send-buffer=4096 –recv-buffer=16384 –num-conns=40000 –num-calls=1

    httperf: warning: open file limit > FD_SETSIZE; limiting max. # of open files to FD_SETSIZE

    Maximum connect burst length: 3

    Total: connections 40000 requests 33288 replies 33216 test-duration 204.995 s

    Connection rate: 195.1 conn/s (5.1 ms/conn, <=16450 concurrent connections)
    Connection time [ms]: min 0.1 avg 12921.3 max 45608.6 median 0.5 stddev 20278.0
    Connection time [ms]: connect 12973.1
    Connection length [replies/conn]: 1.000

    Request rate: 162.4 req/s (6.2 ms/req)
    Request size [B]: 62.0

    Reply rate [replies/s]: min 0.0 avg 162.0 max 400.0 stddev 195.6 (41 samples)
    Reply time [ms]: response 16.2 transfer 0.0
    Reply size [B]: header 38.0 content 5.0 footer 0.0 (total 43.0)
    Reply status: 1xx=0 2xx=33216 3xx=0 4xx=0 5xx=0

    CPU time [s]: user 37.28 system 167.49 (user 18.2% system 81.7% total 99.9%)
    Net I/O: 16.6 KB/s (0.1*10^6 bps)

    Errors: total 6784 client-timo 6784 socket-timo 0 connrefused 0 connreset 0
    Errors: fd-unavail 0 addrunavail 0 ftab-full 0 other 0

    No errors (all 2xx).

    Reply
  30. Brian Hammond says:
    January 11, 2010 at 10:36 pm

    Sorry, you also need: from circuits.net.pollers import EPoll

    I also tried the same test on a monster 2x quad core xeon 5520

    httperf –hog –timeout=60 –client=0/1 –server=localhost –port=10000 –uri=/ –rate=400 –send-buffer=4096 –recv-buffer=16384 –num-conns=40000 –num-calls=1
    Maximum connect burst length: 1

    Total: connections 40000 requests 40000 replies 40000 test-duration 100.001 s

    Connection rate: 400.0 conn/s (2.5 ms/conn, FD_SETSIZE; limiting max. # of open files to FD_SETSIZE

    Maximum connect burst length: 40

    Total: connections 40000 requests 39975 replies 20859 test-duration 114.926 s

    Connection rate: 348.0 conn/s (2.9 ms/conn, <=26959 concurrent connections)
    Connection time [ms]: min 233.0 avg 6634.8 max 72470.5 median 3327.5 stddev 9396.5
    Connection time [ms]: connect 4989.3
    Connection length [replies/conn]: 1.000

    Request rate: 347.8 req/s (2.9 ms/req)
    Request size [B]: 62.0

    Reply rate [replies/s]: min 0.0 avg 189.6 max 1384.3 stddev 392.4 (22 samples)
    Reply time [ms]: response 3230.8 transfer 0.0
    Reply size [B]: header 38.0 content 5.0 footer 0.0 (total 43.0)
    Reply status: 1xx=0 2xx=20859 3xx=0 4xx=0 5xx=0

    CPU time [s]: user 2.43 system 112.48 (user 2.1% system 97.9% total 100.0%)
    Net I/O: 28.7 KB/s (0.2*10^6 bps)

    Errors: total 19141 client-timo 583 socket-timo 0 connrefused 0 connreset 18558
    Errors: fd-unavail 0 addrunavail 0 ftab-full 0 other 0

    Reply
  31. Brian Hammond says:
    January 11, 2010 at 10:38 pm

    Your comment system ate my last comment and spit it back out in 2 parts.

    httperf –hog –timeout=60 –client=0/1 –server=localhost –port=10000 –uri=/ –rate=4000 –send-buffer=4096 –recv-buffer=16384 –num-conns=40000 –num-calls=1

    httperf: warning: open file limit > FD_SETSIZE; limiting max. # of open files to FD_SETSIZE

    Maximum connect burst length: 40

    Total: connections 40000 requests 39975 replies 20859 test-duration 114.926 s

    Connection rate: 348.0 conn/s (2.9 ms/conn, <=26959 concurrent connections)
    Connection time [ms]: min 233.0 avg 6634.8 max 72470.5 median 3327.5 stddev 9396.5
    Connection time [ms]: connect 4989.3
    Connection length [replies/conn]: 1.000

    Request rate: 347.8 req/s (2.9 ms/req)
    Request size [B]: 62.0

    Reply rate [replies/s]: min 0.0 avg 189.6 max 1384.3 stddev 392.4 (22 samples)
    Reply time [ms]: response 3230.8 transfer 0.0
    Reply size [B]: header 38.0 content 5.0 footer 0.0 (total 43.0)
    Reply status: 1xx=0 2xx=20859 3xx=0 4xx=0 5xx=0

    CPU time [s]: user 2.43 system 112.48 (user 2.1% system 97.9% total 100.0%)
    Net I/O: 28.7 KB/s (0.2*10^6 bps)

    Errors: total 19141 client-timo 583 socket-timo 0 connrefused 0 connreset 18558
    Errors: fd-unavail 0 addrunavail 0 ftab-full 0 other 0

    Reply
  32. Brian Hammond says:
    January 11, 2010 at 10:42 pm

    I take that back – there are a number of timeouts and connreset errors. Interesting.

    Reply
    • Nicholas Piël says:
      January 11, 2010 at 11:08 pm

      Thanks for your remarks though.

      The client-timeout errors are caused by the timeout (60seconds in your case) set on the httperf command line. You can make most of these go away by increasing the timeout, but it is interesting to see the difference between all the frameworks. I am not really sure what causes the connection reset errors.

      I am still planning to post an update to this benchmark, but i will need some extra machines for that.

      ps,
      I am curious how the maximum / optimum request rates of both machines differ.

      Reply
  33. Brian Hammond says:
    January 12, 2010 at 12:26 am

    I tried backlog=50000 which resulted in 0 connection reset by peer errors. Why? I do not see the correlation.

    Errors: total 27903 client-timo 27903 socket-timo 0 connrefused 0 connreset 0

    However, there’s a very high number of cient-timeout errors in the same results, even after increasing the httperf timeout to 3m (–timeout=180) which is of course ridiculous.

    —

    Some interesting notes from the httperf man page:

    “client-timo: The number of times a session, connection, or call failed due to a client timeout (as specified by the –timeout and –think-timeout) options.”

    “–timeout=X
    Specifies the amount of time X that httperf is willing to wait for a server reaction. The timeout is specified in seconds and can be a fractional number (e.g., –timeout 3.5). This timeout value is used when establishing a TCP connection, when sending a request, when waiting for a reply, and when receiving a reply. If during any of those activities a request fails to make forward progress within the alloted time, httperf considers the request to have died, closes the associated connection or session and increases the client-timo error count. The actual timeout value used when waiting for a reply is the sum of this timeout and the think-timeout (see option –think-timeout). By default, the timeout value is infinity.”

    —

    Also,

    “Since the machine that httperf runs on has only a finite set of resource available, it can not sustain arbitrarily high HTTP loads. For example, one limiting factor is that there are only roughly 60,000 TCP port numbers that can be in use at any given time. Since on most UNIX systems it takes one minute for a TCP connection to be fully closed (leave the TIME_WAIT state), the maximum rate a client can sustain is at most 1,000 requests per second.”

    —

    Also, it’s general practice to not benchmark from the same host as the server being benchmarked.

    Reply
  34. Brian Hammond says:
    January 12, 2010 at 1:02 am

    Nice, concurrence (trunk) “just works”.

    —

    buffer size (default): 8KiB read + 8KiB write, default backlog of 255:

    httperf –hog –timeout=60 –client=0/1 –server=localhost –port=10000 –uri=/ –rate=1000 –send-buffer=4096 –recv-buffer=16384 –num-conns=40000 –num-calls=1
    httperf: warning: open file limit > FD_SETSIZE; limiting max. # of open files to FD_SETSIZE
    Maximum connect burst length: 1

    Total: connections 40000 requests 40000 replies 40000 test-duration 39.997 s

    Connection rate: 1000.1 conn/s (1.0 ms/conn, FD_SETSIZE; limiting max. # of open files to FD_SETSIZE
    Maximum connect burst length: 1

    Total: connections 40000 requests 40000 replies 40000 test-duration 39.997 s

    Connection rate: 1000.1 conn/s (1.0 ms/conn, FD_SETSIZE; limiting max. # of open files to FD_SETSIZE
    Maximum connect burst length: 6

    Total: connections 40000 requests 40000 replies 40000 test-duration 13.004 s

    Connection rate: 3076.0 conn/s (0.3 ms/conn, <=931 concurrent connections)
    Connection time [ms]: min 0.4 avg 150.7 max 3065.4 median 65.5 stddev 515.1
    Connection time [ms]: connect 93.0
    Connection length [replies/conn]: 1.000

    Request rate: 3076.0 req/s (0.3 ms/req)
    Request size [B]: 62.0

    Reply rate [replies/s]: min 3898.7 avg 3905.5 max 3912.4 stddev 9.7 (2 samples)
    Reply time [ms]: response 57.6 transfer 0.0
    Reply size [B]: header 38.0 content 5.0 footer 0.0 (total 43.0)
    Reply status: 1xx=0 2xx=40000 3xx=0 4xx=0 5xx=0

    CPU time [s]: user 1.03 system 11.98 (user 7.9% system 92.1% total 100.0%)
    Net I/O: 315.4 KB/s (2.6*10^6 bps)

    Errors: total 0 client-timo 0 socket-timo 0 connrefused 0 connreset 0
    Errors: fd-unavail 0 addrunavail 0 ftab-full 0 other 0

    —

    looks good to me.

    Reply
  35. Brian Hammond says:
    January 12, 2010 at 1:03 am

    grrr, your comment system is completely fuxored.

    Reply
  36. James Mills says:
    January 12, 2010 at 12:18 pm

    Hi all,

    James here (prologic) the developer of circuits.
    Brian has brought my attention to this very
    interesting blog and I must say I’m a bit
    disappointed with circuits’ results, however
    I am not surprised. I haven’t quite been able
    to perfect the EPoll Component in
    circuits.net.pollers and I believe the performance
    penalty you see in the results here to be a defect.

    I’m having another look to see if I can rectify this
    in the development branch (http://hg.softcircuit.com.au/projects/circuits-dev/).

    Thanks for the nice comment about circuits having
    a nice clean implementation and the circuits version
    of pongserver having the most beautiful code of them all.
    (“code is the most beautiful of them all,”).

    cheers
    James
    cheers
    James

    Reply
  37. Brian Hammond says:
    January 18, 2010 at 4:44 pm

    You need to look at Circuits again. James’ latest patch makes the epoll support level-triggered instead of edge-triggered. There are no errors (all 200) now.

    http://gist.github.com/280103

    Reply
  38. Anton says:
    February 8, 2010 at 8:31 pm

    What is circuits, what is tornado, what is geven on the graphic??? I see three blue colors! :(

    Reply
  39. Bill Janssen says:
    February 8, 2010 at 10:29 pm

    Thanks for doing this! I’d be interested in the Medusa results — that’s what I’m currently using for UpLib (http://uplib.parc.com/). I keep wondering if I should shift to something else, but so far Medusa seems to keep working just fine.

    The current Medusa is at http://www.amk.ca/python/code/medusa.html.

    Reply
  40. Shannon -jj Behrens says:
    February 10, 2010 at 8:04 pm

    I can’t thank you enough for writing this awesome blog post. I recently gave a talk about Python concurrency, http://jjinux.blogspot.com/2009/12/python-concurrency.html, but I think I like your blog post even better since it shows the hard data and real code.

    Reply
  41. Integrate Tornado in Django — geek scrap says:
    February 11, 2010 at 1:38 am

    [...] For more performance info, James Abley pointed me to a very complete benchmark of available Python asynchronous webservers. It looks like Tornado is a real monster of [...]

    Reply
  42. Ryan Williams says:
    February 17, 2010 at 8:45 am

    Hi there, just poking you to see whether you’re going to update these with the corrected code that all the maintainers of these libraries have posted. You said you’d get to it “somewhere after xmas”….it’s definitely after xmas!

    Reply
  43. Francisco Ribeiro says:
    February 17, 2010 at 5:55 pm

    Nice post.

    Kamaelia project is missing.

    Reply
  44. Why gevent « Concurrency in Python says:
    February 27, 2010 at 4:04 pm

    [...] The superior performance is not the only benefit of tight integration with libevent. Other benefits are [...]

    Reply
  45. Jordan Fung says:
    March 10, 2010 at 4:30 am

    I’m using gevent.http to rewrite your test code and get 70% faster :) , gevent is soooooooo cool!

    Reply
  46. Jason says:
    April 22, 2010 at 11:41 pm

    This is excellent research. I’m currently looking for an async framework and I hadn’t even heard of gevent. Thank you for putting this together and publishing it.

    Reply
  47. Péter Szabó says:
    May 18, 2010 at 12:19 am

    Great, informative article!

    FYI Syncess, a high performance asynchronous client and server library using Stackless Python has born after this comparison. See Syncless at http://code.google.com/p/syncless/

    I’ve also written a feature comparison of event-driven and coroutine-based non-blocking I/O libraries for Python. Read it at: http://ptspts.blogspot.com/2010/05/feature-comparison-of-python-non.html

    Reply
  48. Tom Burdick says:
    July 18, 2010 at 8:07 pm

    Just as a quick comparison I created a client based around pyev, here is the program

    import socket
    import signal
    import pyev
    
    class Client(object):
        def start(self, loop, sock):
            self.msg = "HTTP/1.0 200 OK\r\nContent-Length: 5\r\n\r\nPong!\r\n"
            self.sock = sock
            self.write = self.write_msg
            self.writewatcher = pyev.Io(self.sock, pyev.EV_WRITE, loop, self.write)
            self.writewatcher.start()
    
        def write_msg(self, watcher, events):
            self.sock.send(self.msg)
            self.writewatcher.stop()
            self.sock.close()
    
    class Server(object):
        def start(self, loop):
            self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.sock.bind(('localhost', 10000))
            self.sock.listen(500)
            self.acceptwatcher = pyev.Io(self.sock, pyev.EV_READ, loop, self.accept)
            self.acceptwatcher.start()
    
        def accept(self, watcher, events):
            (sock, addr) = self.sock.accept()
            client = Client()
            client.start(watcher.loop, sock)
    
    def interrupt(watcher, events):
        watcher.loop.unloop()
    
    loop = pyev.default_loop()
    sigwatch = pyev.Signal(signal.SIGINT, loop, interrupt)
    sigwatch.start()
    srv = Server()
    srv.start(loop)
    loop.loop()
    

    Here is the results using ra=10000

    httperf –hog –server localhost –num-conn 40000 –port 10000 –ra 10000 –timeout 5
    httperf –hog –timeout=5 –client=0/1 –server=localhost –port=10000 –uri=/ –rate=10000 –send-buffer=4096 –recv-buffer=16384 –num-conns=40000 –num-calls=1
    Maximum connect burst length: 41

    Total: connections 40000 requests 40000 replies 40000 test-duration 3.997 s

    Connection rate: 10008.5 conn/s (0.1 ms/conn, <=57 concurrent connections)
    Connection time [ms]: min 0.1 avg 0.3 max 4.5 median 0.5 stddev 0.2
    Connection time [ms]: connect 0.1
    Connection length [replies/conn]: 1.000

    Request rate: 10008.5 req/s (0.1 ms/req)
    Request size [B]: 62.0

    Reply rate [replies/s]: min 0.0 avg 0.0 max 0.0 stddev 0.0 (0 samples)
    Reply time [ms]: response 0.2 transfer 0.0
    Reply size [B]: header 38.0 content 5.0 footer 0.0 (total 43.0)
    Reply status: 1xx=0 2xx=40000 3xx=0 4xx=0 5xx=0

    CPU time [s]: user 1.19 system 2.79 (user 29.9% system 69.9% total 99.7%)
    Net I/O: 1026.3 KB/s (8.4*10^6 bps)

    Errors: total 0 client-timo 0 socket-timo 0 connrefused 0 connreset 0
    Errors: fd-unavail 0 addrunavail 0 ftab-full 0 other 0

    Note that the sockets are in fact blocking, I never set nonblocking, I have no doubt I could do even better with nonblocking.

    If this bench is saying anything, its saying nothing comes even close to pyev in the python world, nothing.

    Just to really push things, I tried 20k per second…

    httperf –hog –server localhost –num-conn 40000 –port 10000 –ra 20000 –timeout 5
    httperf –hog –timeout=5 –client=0/1 –server=localhost –port=10000 –uri=/ –rate=20000 –send-buffer=4096 –recv-buffer=16384 –num-conns=40000 –num-calls=1
    Maximum connect burst length: 33

    Total: connections 40000 requests 40000 replies 40000 test-duration 2.004 s

    Connection rate: 19963.5 conn/s (0.1 ms/conn, <=56 concurrent connections)
    Connection time [ms]: min 0.1 avg 0.5 max 2.7 median 0.5 stddev 0.2
    Connection time [ms]: connect 0.3
    Connection length [replies/conn]: 1.000

    Request rate: 19963.5 req/s (0.1 ms/req)
    Request size [B]: 62.0

    Reply rate [replies/s]: min 0.0 avg 0.0 max 0.0 stddev 0.0 (0 samples)
    Reply time [ms]: response 0.3 transfer 0.0
    Reply size [B]: header 38.0 content 5.0 footer 0.0 (total 43.0)
    Reply status: 1xx=0 2xx=40000 3xx=0 4xx=0 5xx=0

    CPU time [s]: user 0.35 system 1.64 (user 17.6% system 81.8% total 99.5%)
    Net I/O: 2047.0 KB/s (16.8*10^6 bps)

    Errors: total 0 client-timo 0 socket-timo 0 connrefused 0 connreset 0
    Errors: fd-unavail 0 addrunavail 0 ftab-full 0 other 0

    And finally how the twisted example does on my machine in comparison, exactly as taken from this blog when given a rate of 10k…

    httperf –hog –server localhost –num-conn 40000 –port 10000 –ra 10000 –timeout 5
    httperf –hog –timeout=5 –client=0/1 –server=localhost –port=10000 –uri=/ –rate=10000 –send-buffer=4096 –recv-buffer=16384 –num-conns=40000 –num-calls=1
    Maximum connect burst length: 16

    Total: connections 26269 requests 26269 replies 26269 test-duration 6.261 s

    Connection rate: 4195.4 conn/s (0.2 ms/conn, <=1022 concurrent connections)
    Connection time [ms]: min 0.5 avg 152.4 max 3162.8 median 116.5 stddev 305.9
    Connection time [ms]: connect 31.7
    Connection length [replies/conn]: 1.000

    Request rate: 4195.4 req/s (0.2 ms/req)
    Request size [B]: 62.0

    Reply rate [replies/s]: min 5247.1 avg 5247.1 max 5247.1 stddev 0.0 (1 samples)
    Reply time [ms]: response 120.7 transfer 0.0
    Reply size [B]: header 38.0 content 5.0 footer 0.0 (total 43.0)
    Reply status: 1xx=0 2xx=26269 3xx=0 4xx=0 5xx=0

    CPU time [s]: user 0.24 system 6.00 (user 3.9% system 95.9% total 99.8%)
    Net I/O: 430.2 KB/s (3.5*10^6 bps)

    Errors: total 13731 client-timo 0 socket-timo 0 connrefused 0 connreset 0
    Errors: fd-unavail 13731 addrunavail 0 ftab-full 0 other 0

    It basically fails to even finish…

    Reply
  49. Tornado on Python at MARVELOUS STUFFS says:
    September 16, 2010 at 1:32 am

    [...] a minimalist framework in Python! You can read more about it in their main website. Also check this article which compares the asynchronous servers in python. Tornado walks its [...]

    Reply
  50. jonson says:
    October 25, 2010 at 6:46 pm

    I2JfUx http://cra3Zzphu47hvm4bbmp82f0vwJs.com

    Reply
  51. [Python] quick survey of asynchronous frameworks « 軟件開發原始人的進化史 says:
    October 26, 2010 at 10:42 am

    [...] Asynchronous Servers in Python [...]

    Reply
  52. gc says:
    December 15, 2010 at 5:24 pm

    Your benchmarks are tainted because you are not using the same backlog value to the listen call. Likewise, some are simply using the default value provided by the framework. As an example, Twisted is using 5000 while gevent is using 500. Who knows what is being used for the other frameworks.

    This deviation is value has implications for all of your tests, but especially so for the error and connection rate tests. Please standardize on a single listen value and re-run your tests.

    Ignoring the above error, your summary of async/network/event frameworks available for Python is very interesting and informative.

    Reply
  53. Gvlhfsjb says:
    February 11, 2011 at 8:39 pm

    It’s funny goodluck redtube sick =-P free girl boy redtube 916 red tube tranny alone 110125 red tube lesbo oqvzc hot erotic oil les massage redtube 0721 redtube slim teen 0883 redtube squirting videos %-DDD red tube tied fxyv redtube teen sucks dysqz redtube jenna jamason 95862

    Reply
  54. Icuubyzn says:
    February 27, 2011 at 2:28 am

    It’s serious russian preteen beauties 462368 preteen gay story =-DDD preteen bikini competition 4439 12yr old preteen 682 preteen models paradise :-OO preteens naked galleries 813464 preteens nude preview 86873 preteen russian mpeg ebmsf preteen pantyhose fun 729 preteen girl model11 gxz

    Reply
  55. Nicholas Piël » Announcing our new startup: SiteSupport says:
    March 3, 2011 at 5:18 pm

    [...] over a websocket connection. This requirement of websocket connections is the reason why I  benchmarked various event-driven servers about a year ago. The benchmark results showed that the servers which [...]

    Reply
  56. :: Твой Интернет says:
    April 19, 2011 at 7:50 am

    [...] проекта. Заинтересовавшись, я нашёл ещё вот этот и этот тесты. В них Торнадо показал себя достаточно неплохо. [...]

    Reply
  57. Tornado on Python at Personal Musing and Software Development Stuffs says:
    July 15, 2011 at 7:20 pm

    [...] a minimalist framework in Python! You can read more about it in their main website. Also check this article which compares the asynchronous servers in python. Tornado walks its [...]

    Reply
  58. 一些有用的Python函式庫列表 | Ben's Computer Graphics Blog says:
    August 1, 2011 at 8:39 am

    [...] Asynchronous Servers in Python [...]

    Reply

Leave a Reply

Click here to cancel reply.

SiteSupport

Working on:

SiteSupport - Remote desktop for web apps
remote desktop for web apps

We've just launched our first product demo, check it out!

Posts

  • Announcing: SiteSupport
  • ZeroMQ an introduction
  • Benchmark of Python WSGI Servers
  • Asynchronous Servers in Python
  • Person Recognition (with Python)

Tags

ai async cdn comet computer vision gevent javascript performance programming Python rant scalability sitesupport websockets wsgi zeromq

Follow

Follow on Twitter
Subscribe to the RSS feed
Receive updates by Email

Running on Wordpress
design based on Freshy by Jidé, the nutmeg image is from Shlomit & Ziv
(c) Nicholas Piël