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 5 to 1

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!

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.

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.

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

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.

— 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
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
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
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
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
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New

Other Trending Topics Top

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
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews