So far, benchmarks are favorable. From my intial profiling, it seems most of the
time is spent in CFFI when on Windows, but in linux (of course) CFFI is a minor
speed bump, and the actual
cl-async:* functions are the main slowdown (which is
good). Because of this, I really recommend running any production server on
linux. This isn’t so much because Windows sucks, but because I feel like most
lisp implementations focus on linux performance a lot more than Windows (at
least when it comes to CFFI).
On my (already crowded) Linode 512, cl-async (for both tcp-server was able to process about 40K concurrent requests with this example before running out of memory:
(defparameter *http-response* (babel:string-to-octets (with-output-to-string (s) (format s "HTTP/1.1 200 OK~c~c" #\return #\newline) (format s "Date: Wed, 03 Oct 2012 23:43:10 GMT~c~c" #\return #\newline) (format s "Content-Type: text/plain~c~c" #\return #\newline) (format s "Content-Length: 9~c~c" #\return #\newline) (format s "~c~c" #\return #\newline) (format s "omglolwtf")))) (defun tcp-server-test (&key stats) (as:start-event-loop (lambda () (format t "Starting TCP server.~%") (let ((listener nil) (quit nil) (finished-requests 0) (last-finished 0) (last-time 0)) (setf listener (as:tcp-server nil 9009 (lambda (socket data) (declare (ignore data)) (as:delay (lambda () (unless (as:socket-closed-p socket) (as:write-socket-data socket *http-response* :write-cb (lambda (socket) (as:close-socket socket) (incf finished-requests))))) :time 5)) (lambda (err) (format t "tcp server event: ~a~%" err)))) (as:signal-handler 2 (lambda (sig) (declare (ignore sig)) (setf quit t) (as:free-signal-handler 2) (as:close-tcp-server listener))) (labels ((show-stats () (let* ((stats (as:stats)) (incoming (getf stats :incoming-tcp-connections)) (outgoing (getf stats :outgoing-tcp-connections)) (now (get-internal-real-time)) (sec (/ (- now last-time) internal-time-units-per-second)) (rate (/ (- finished-requests last-finished) sec))) (setf last-finished finished-requests last-time now) (format t "incoming: ~a~%outgoing: ~a~%finished: ~a~%rate: ~f req/s~%~%" incoming outgoing finished-requests rate)) (unless quit (as:delay #'show-stats :time 1)))) (when stats (show-stats))))) :catch-app-errors t) (format t "TCP server exited.~%")) ;; run it (tcp-server-test :stats t)
What’s happening here is that the server gets a request, delays 5 seconds, then responds on the same socket. This allows connections to build up for 5 seconds before they start getting released, which is a good way to test how many connections it can handle.
On another neighboring Linode, I ran
httperf --server=18.104.22.168 --port=9009 --num-conns=40000 --num-calls=10 --hog --rate=6000
In the stats output, I was getting:
incoming: 12645 outgoing: 0 finished: 7330 rate: 6026.183 req/s
So I was getting ~6000k req/s, and in some tests (longer delay value) I was able
to get the “incoming” connections to 40K. 6000/s seems to be the limit of the
httperf was running on, not the server, but I can’t confirm this yet.
From the tests I ran, memory seems to be the number one constraining factor in
scalability of number of connections. The more memory, the more connections can