william_os4y ([info]william_os4y) wrote,
@ 2006-10-12 19:03:00
Previous Entry  Add to memories!  Tell a Friend  Next Entry
1024 requests per second with a wsgi python web server
Yes, we can make fast web server with python!

I'm quite amazed to see the speed of the python web server I'm building: 1024#/sec!!!. (via ab2 -n3000 -c50)

My goal is to have the fastest python web server. After several research, trial, ... I've found apricot. If you look at my past posts, you'll see how fast it is.
Because this code contains errors and because the author does not answer to my mails, I've decided to start a new one from scratch; but still based on pyevent.
Then it will be easier to implement WSGI. This will them be the fastest WSGI web server ? ;-).

Because of libevent, this is an "event" web server. This could be seen has a drawback, but has several advantages too.

The tests were made between 2 machines connected via a switched network of 100Mb and by using ab2 (the Apache benchmark tool).

I'm exited by such performance result and was impatient to share it.

For sure, as soon as the code will be "polished", I'll make it available for everyone.

William



(8 comments) - (Post a new comment)


(Anonymous)
2006-10-12 09:05 pm UTC (link)
On an older (1.26Ghz) server, accessing a web framework (not just returning a string):

ab -c50 -n3000 http://localhost:8888/10/
Requests per second: 685.71 [#/sec] (mean)

(Reply to this) (Thread)


(Anonymous)
2006-10-12 09:07 pm UTC (link)
Same setup, accessing the "root" / object (which is a html/text reply 544 bytes long):
Requests per second: 771.60 [#/sec] (mean)

So I'm not surprised that a fast Python server can be made; there are already *fast* python frameworks! A happy thing.

(Reply to this) (Parent)

_cpwsgiserver benchmark
(Anonymous)
2006-10-12 11:30 pm UTC (link)
CherryPy's _cpwsgiserver is already faster than that (on my Win2k laptop, 2GHz, 512M) with a simple WSGI Hello World:

threads | Completed | Failed | req/sec | msec/req | KB/sec |
     10 |      1000 |      0 | 1264.00 |    0.791 | 213.62 |
     20 |      1000 |      0 | 1248.20 |    0.801 | 210.95 |
     30 |      1000 |      0 | 1217.76 |    0.821 | 205.80 |
     40 |      1000 |      0 | 1203.09 |    0.831 | 203.32 |
     50 |      1000 |      0 | 1188.76 |    0.841 | 200.90 |

(Reply to this) (Thread)

Re: _cpwsgiserver benchmark
[info]william_os4y
2006-10-13 04:44 pm UTC (link)
I'm interested to get details about your config. I'm maybe doing wrong things ;-(.

I've taken the last Cherrypy version and using the tut01_helloworld.py.

Here my results:

Server Software: CherryPy/3.0.0beta2
Server Hostname: localhost
Server Port: 8080

Document Path: /
Document Length: 12 bytes

Concurrency Level: 50
Time taken for tests: 4.636 seconds
Complete requests: 3000
Failed requests: 0
Broken pipe errors: 0
Total transferred: 426000 bytes
HTML transferred: 36000 bytes
Requests per second: 647.11 [#/sec] (mean)
Time per request: 77.27 [ms] (mean)
Time per request: 1.55 [ms] (mean, across all concurrent requests)
Transfer rate: 91.89 [Kbytes/sec] received



Just as reference, the result of my server (same machines, same conditions, ...)


Server Software: fapws/0.4.4
Server Hostname: localhost
Server Port: 80

Document Path: /hello
Document Length: 11 bytes

Concurrency Level: 50
Time taken for tests: 2.603 seconds
Complete requests: 3000
Failed requests: 0
Broken pipe errors: 0
Total transferred: 342000 bytes
HTML transferred: 33000 bytes
Requests per second: 1152.52 [#/sec] (mean)
Time per request: 43.38 [ms] (mean)
Time per request: 0.87 [ms] (mean, across all concurrent requests)
Transfer rate: 131.39 [Kbytes/sec] received


(Reply to this) (Parent)(Thread)

Re: _cpwsgiserver benchmark
(Anonymous)
2006-10-13 08:13 pm UTC (link)
You're using the full CherryPy stack, then, not just the WSGI server. I used a simple WSGI "Hello World" app (with _cpwsgiserver only).

import re
import sys
import threading
import time

from cherrypy import _cpmodpy

AB_PATH = ""
APACHE_PATH = "apache"
SCRIPT_NAME = ""
PORT = 8080


class ABSession:
    """A session of 'ab', the Apache HTTP server benchmarking tool."""
    
    parse_patterns = [('complete_requests', 'Completed',
                       r'^Complete requests:\s*(\d+)'),
                      ('failed_requests', 'Failed',
                       r'^Failed requests:\s*(\d+)'),
                      ('requests_per_second', 'req/sec',
                       r'^Requests per second:\s*([0-9.]+)'),
                      ('time_per_request_concurrent', 'msec/req',
                       r'^Time per request:\s*([0-9.]+).*concurrent requests\)$'),
                      ('transfer_rate', 'KB/sec',
                       r'^Transfer rate:\s*([0-9.]+)'),
                      ]
    
    def __init__(self, path=SCRIPT_NAME + "/", requests=3000, concurrency=10):
        self.path = path
        self.requests = requests
        self.concurrency = concurrency
    
    def args(self):
        assert self.concurrency > 0
        assert self.requests > 0
        return ("-k -n %s -c %s http://localhost:%s%s" %
                (self.requests, self.concurrency, PORT, self.path))
    
    def run(self):
        # Parse output of ab, setting attributes on self
        args = self.args()
        self.output = _cpmodpy.read_process(AB_PATH or "ab", args)
        for attr, name, pattern in self.parse_patterns:
            val = re.search(pattern, self.output, re.MULTILINE)
            if val:
                val = val.group(1)
                setattr(self, attr, val)
            else:
                setattr(self, attr, None)


safe_threads = (25, 50, 100, 200, 400)
if sys.platform in ("win32",):
    # For some reason, ab crashes with > 50 threads on my Win2k laptop.
    safe_threads = (10, 20, 30, 40, 50)


def thread_report(path=SCRIPT_NAME + "/", concurrency=safe_threads):
    sess = ABSession(path)
    attrs, names, patterns = zip(*sess.parse_patterns)
    rows = [('threads',) + names]
    for c in concurrency:
        sess.concurrency = c
        sess.run()
        rows.append([c] + [getattr(sess, attr) for attr in attrs])
    return rows

def print_report(rows):
    widths = []
    for i in range(len(rows[0])):
        lengths = [len(str(row[i])) for row in rows]
        widths.append(max(lengths))
    for row in rows:
        print
        for i, val in enumerate(row):
            print str(val).rjust(widths[i]), "|",
    print


if __name__ == '__main__':
    
    def simple_app(environ, start_response):
          """Simplest possible application object""" 
          status = '200 OK'
          response_headers = [('Content-type','text/plain'),
                              ('Content-Length','19')]
          start_response(status, response_headers)
          return ['My Own Hello World!']
    
    from cherrypy import _cpwsgiserver as w
    s = w.CherryPyWSGIServer(("localhost", PORT), simple_app)
    threading.Thread(target=s.start).start()
    try:
        time.sleep(1)
        print_report(thread_report())
    finally:
        s.stop()

(Reply to this) (Parent)

(Reply from suspended user)
apricot
(Anonymous)
2006-10-13 06:28 pm UTC (link)
I'd be happy to answer emails; I'm not sure what address you're using, but I don't remember seeing any from you.

Try using jamie at polimetrix com

However, I wouldn't be surprised if apricot had errors. It's a very limited HTTP server... more of a toy project than anything, a product of one afternoon playing with libevent before engaging in more serious things.

FWIW, I have another webserver written on top of a libevent-based framework that completely implements HTTP/1.1--that one is quite a bit more robust. On good hardware, it does 1200-1400 req/s without keepalive, and goes quite a bit faster than that with keepalive.

- Jamie Turner

(Reply to this)

(Reply from suspended user)

(8 comments) - (Post a new comment)

Create an Account
Forgot your login or password?
Login w/ OpenID
English • Español • Deutsch • Русский…