Friday, February 18, 2011

Fun with LuaJIT and FFI library - httpd microbenchmarking

When I saw the new FFI library in the LuaJIT project, I immediately thought "yummy". I thought I'd give it a shot during the evening, but.... curiosity had killed the cat, so...

First thing that got annoying quite quickly was the need to prefix every C function call with ffi.C. That's annoying. Can we do better ? Yes, we can. We add this:

-- try to use FFI when no lua symbol found...
setmetatable(_G, { __index = ffi.C } )


And the life immediately becomes great - you get the power of the declared functions plus the flexibility of the lua language, so you can write stuff like "local s = socket(AF_INET6, SOCK_STREAM, 0)". Tres cool.

Now, let's see how much this all is worth performance wise.

Let's write a funny high-level wrapper, that at its "application" core will look like this:

local HTTP_REPLY = [[HTTP/1.0 200 OK
Content-Type: text/plain

This is test
]]

local MAX_FD = 2560

local ss = socket_set(MAX_FD)

local my_accept_cb = function(fds, i)
local cb = {}
cb.read = function(fds, i, data, len)
fds.send(i, HTTP_REPLY, #HTTP_REPLY)
fds.close(i)
end
cb.close = function(fds, i)
-- print("Closed socket")
end
return cb
end

while not ss.add_listener(12345, my_accept_cb) do
sleep(1)
end
print("Added listener, please run the test")
while true do
local n = ss.poll(1000)
end


What do we do here ? We create a high-level abstraction which I called a "socket set" - that encapsulates all the boring event loop that usually exists in programs; and then add a listener socket on port 12345 to this set, which has a callback that is called upon accept and can return either the table with callbacks for the newly accepted socket - or nil - then the connection will be closed immediately.

The accepted socket callbacks are such that they implement a very naive "HTTP server" - just for microbenchmarking.

Now, let's give it a whirl with ab.

The resuls are quite entertaining, here is how they look like:

# while (true); do ab -n 1000 -c 1000 http://localhost:12345/ 2>&1 | grep "Requests per"; done
Requests per second: 8433.91 [#/sec] (mean)
Requests per second: 12374.55 [#/sec] (mean)
Requests per second: 12720.06 [#/sec] (mean)
Requests per second: 12688.58 [#/sec] (mean)
Requests per second: 13144.58 [#/sec] (mean)
Requests per second: 10742.29 [#/sec] (mean)
Requests per second: 8651.27 [#/sec] (mean)
Requests per second: 12748.76 [#/sec] (mean)
Requests per second: 8145.58 [#/sec] (mean)
Requests per second: 12946.32 [#/sec] (mean)
Requests per second: 8648.65 [#/sec] (mean)
Requests per second: 13047.85 [#/sec] (mean)
Requests per second: 11550.14 [#/sec] (mean)
Requests per second: 12904.22 [#/sec] (mean)
Requests per second: 12968.32 [#/sec] (mean)
Requests per second: 13219.47 [#/sec] (mean)
Requests per second: 8244.09 [#/sec] (mean)
Requests per second: 12056.47 [#/sec] (mean)
Requests per second: 12834.83 [#/sec] (mean)
Requests per second: 13288.86 [#/sec] (mean)
Requests per second: 11455.02 [#/sec] (mean)
Requests per second: 11130.03 [#/sec] (mean)
Requests per second: 8034.32 [#/sec] (mean)
Requests per second: 12566.76 [#/sec] (mean)


This is not too bad at all.

Here is the similar test with lighttpd running on the same machine, serving the default static page:


Requests per second: 12080.50 [#/sec] (mean)
Requests per second: 9397.88 [#/sec] (mean)
Requests per second: 9948.47 [#/sec] (mean)
Requests per second: 12906.39 [#/sec] (mean)
Requests per second: 9284.53 [#/sec] (mean)
Requests per second: 4281.34 [#/sec] (mean)
Requests per second: 9143.44 [#/sec] (mean)
Requests per second: 12422.21 [#/sec] (mean)
Requests per second: 9170.19 [#/sec] (mean)
Requests per second: 12603.03 [#/sec] (mean)
Requests per second: 9413.54 [#/sec] (mean)
Requests per second: 12981.62 [#/sec] (mean)
Requests per second: 8615.56 [#/sec] (mean)
Requests per second: 9849.98 [#/sec] (mean)
Requests per second: 9869.43 [#/sec] (mean)
Requests per second: 9818.84 [#/sec] (mean)
Requests per second: 3384.56 [#/sec] (mean)
Requests per second: 9127.59 [#/sec] (mean)
Requests per second: 1528.56 [#/sec] (mean)
Requests per second: 9157.01 [#/sec] (mean)


Not bad at all for a high-level code, what do you think ?

If you want to toy with it yourself - it's on github.

No comments: