I feel like I’m doing something silly. I’m just trying to compare a dummy http server to cowboy and even though my “implementation” is absolutely incomplete, I’m getting considerably worse performance. I figure I’m missing something fundamental.
I know this isn’t a valid http server. I know I can’t just recv once and get an entire payload (though, in this case it’s fine because the payloads are very small)
Here’s what I have:
def run() do
tcp_opts = [:binary, packet: :raw, active: false, nodelay: true, send_timeout_close: true, reuseaddr: true, backlog: 1024]
{:ok, socket} = :gen_tcp.listen(8420, tcp_opts)
accept_loop(socket)
end
defp accept_loop(listen_socket) do
{:ok, client_socket} = :gen_tcp.accept(listen_socket) do
pid = spawn fn -> read_loop(client_socket) end
:gen_tcp.controlling_process(client_socket, pid)
accept_loop(listen_socket)
end
defp read_loop(socket) do
{:ok, data} = :gen_tcp.recv(socket, 0)
conn = %Plug.Conn{
port: 8420,
scheme: :http,
method: "GET",
owner: self(),
path_info: [],
query_string: "",
req_headers: %{},
request_path: "",
host: "127.0.0.1",
remote_ip: {127, 0, 0, 1},
adapter: {__MODULE__, socket},
}
Router.call(conn, [])
read_loop(socket)
end
def send_resp(socket, _status, _headers, _body) do
res = [
"HTTP/1.1 404 Not Found\r\n",
"Content-Length: 0\r\n\r\n"
]
:ok = :gen_tcp.send(socket, res)
{:ok, nil, socket}
end
Router
is just a Plug router (what you’d pass as :plug
to the Plug.Cowboy.
I’m using autocannon. It’s only starting 10 connections and then doing keepalive (so I doubt the problem is in the accept loop). Cowboy does ~30K/second. This code does a bit less than half. This seems incredible to me given that Cowboy actually has to parse the request and worry about TCP fragmentation.
Anyone know what’s up?