kseg

kseg

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?

Showing Posts 1 to 5

OvermindDL1

OvermindDL1

Well first note, you don’t want to use :gen_tcp.recv for the fastest speed, you probably want to use active: :once or whatever it was with async messaging back into the app (or if you don’t have to worry about reading messages fast enough then just active true). A recv is synchronous and involves even more messages, so that would be one of a few causes of slowdowns there, but the first to fix. :slight_smile:

The next step to optimize is that when you receive a message with active: once then you probably want to go to a full active loop until no message in X milliseconds, at which point drop back to active: once.

Though if this is a completely arbitrary ‘fast-as-possible’ test then just staying fully active receiving would be fastest.

OvermindDL1

OvermindDL1

Have a couple more minutes right now, looking more.

Could start up a couple of listen sockets on that same port in different processes for faster accepting, in ‘some’ cases it helps.

Also I see you are using :gen_tcp.send here as well, this is also synchronous for a response from a successful send from the OS and can slow you down a good bit if you are using keepalive to handle many requests on a single socket, it can be easiest just to slave it out to another process and then immediately receive for the next packet. However as a single connection on HTTP1 is serialized anyway, it’s not a big deal, just inet setopt to do active: :once again then call send in that order and you’ll have a packet waiting in your mailbox about the time it returns.

There are lots of other things that can be done, but at the very least I’d use accept: :once or accept: true depending. For this specific case (although someone could massively slow down the server in that case by sending tons of packets) active: true would probably be fastest, but active: :once is both quite fast (much more so then recv/2 and safe.

OvermindDL1

OvermindDL1

This is super low level stuff so ignore this post unless you really really want every microsecond, but you can bypass most of the :gen_tcp.send cost by sending asynchronously so the socket port directly via low level port commands, you can even get a notification back that way allowing it to fail (and it would tell you) so you know when to back off if you are sending too fast.

xlphs

xlphs

I typically create or set socket with active: false then send message to genserver itself periodically to receive using :inet.setopts(socket, active: 1). And definitely create a new process for each client with :gen_tcp.controlling_process/2. In the rare case the protocol dictates more message is expected after parsing then use :gen_tcp.recv/2.

kseg

kseg OP

Thanks. I’ll try that. I initially had a more complete parser with active: once..but I started to dumb it down to try to figure out what was going on. active: once wasn’t any faster than recv but setting it before the send is a good idea!

— All posts loaded —

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
kpanic
Hi everyone, I am toying with the idea of building a “match maker” for giving personal help to people that wants to start coding. I sta...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
asweet-confluent
I recently noticed that Elixir’s Logger defaults its primary log level to :debug when no :logger, :level application configuration is pre...
New
apz
I’m new to elixir and just tried to install the elixirLS extension for VScode(ium) and it is throwing some errors that I would like help ...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews