Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Finch.request/3: Use improper list and avoid Enum.reverse #286

Merged
merged 1 commit into from
Aug 5, 2024

Conversation

wojtekmach
Copy link
Contributor

I tried benchmarking this and it's inconclusive but I think conceptually it makes more sense.

{:ok, _} =
  Bandit.start_link(
    port: 4000,
    plug: fn conn, _ ->
      conn = Plug.Conn.send_chunked(conn, 200)

      Enum.reduce(1..1000, conn, fn _, conn ->
        {:ok, conn} = Plug.Conn.chunk(conn, String.duplicate("0", 1000))
        conn
      end)
    end
  )

{:ok, _} = Finch.start_link(name: :a)
{:ok, _} = Finch.start_link(name: :b)

req = Finch.build(:get, "http://localhost:4000")

Benchee.run(
  %{
    "a" => fn ->
      fun = fn
        {:status, value}, {_, headers, body, trailers} ->
          {:cont, {value, headers, body, trailers}}

        {:headers, value}, {status, headers, body, trailers} ->
          {:cont, {status, headers ++ value, body, trailers}}

        {:data, value}, {status, headers, body, trailers} ->
          {:cont, {status, headers, [value | body], trailers}}

        {:trailers, value}, {status, headers, body, trailers} ->
          {:cont, {status, headers, body, trailers ++ value}}
      end

      {:ok, {200, _headers, body, _trailers}} =
        Finch.stream_while(req, :a, {nil, [], [], []}, fun)

      body |> Enum.reverse() |> IO.iodata_to_binary() |> byte_size()
    end,
    "b" => fn ->
      fun = fn
        {:status, value}, {_, headers, body, trailers} ->
          {:cont, {value, headers, body, trailers}}

        {:headers, value}, {status, headers, body, trailers} ->
          {:cont, {status, headers ++ value, body, trailers}}

        {:data, value}, {status, headers, body, trailers} ->
          {:cont, {status, headers, [body | value], trailers}}

        {:trailers, value}, {status, headers, body, trailers} ->
          {:cont, {status, headers, body, trailers ++ value}}
      end

      {:ok, {200, _headers, body, _trailers}} =
        Finch.stream_while(req, :b, {nil, [], [], []}, fun)

      body |> IO.iodata_to_binary() |> byte_size()
    end
  },
  time: 10,
  memory_time: 2
)

Results:

12:00:04.273 [info] Running #Function<0.20032135 in file:bench.exs> with Bandit 1.5.3 at 0.0.0.0:4000 (http)
Operating System: macOS
CPU Information: Apple M2
Number of Available Cores: 8
Available memory: 24 GB
Elixir 1.18.0-dev
Erlang 27.0.1
JIT enabled: true

Benchmark suite executing with the following configuration:
warmup: 2 s
time: 10 s
memory time: 2 s
reduction time: 0 ns
parallel: 1
inputs: none specified
Estimated total run time: 28 s

Benchmarking a ...
Benchmarking b ...
Calculating statistics...
Formatting results...

Name           ips        average  deviation         median         99th %
a            95.77       10.44 ms     ±2.19%       10.43 ms       11.21 ms
b            95.43       10.48 ms     ±2.02%       10.46 ms       11.13 ms

Comparison:
a            95.77
b            95.43 - 1.00x slower +0.0377 ms

Memory usage statistics:

Name         average  deviation         median         99th %
a            2.24 MB     ±0.50%        2.24 MB        2.25 MB
b            2.23 MB     ±0.35%        2.23 MB        2.24 MB

Comparison:
a            2.24 MB
b            2.23 MB - 1.00x memory usage -0.01002 MB

I tried benchmarking this and it's inconclusive but I think conceptually
it makes more sense.

    {:ok, _} =
      Bandit.start_link(
        port: 4000,
        plug: fn conn, _ ->
          conn = Plug.Conn.send_chunked(conn, 200)

          Enum.reduce(1..1000, conn, fn _, conn ->
            {:ok, conn} = Plug.Conn.chunk(conn, String.duplicate("0", 1000))
            conn
          end)
        end
      )

    {:ok, _} = Finch.start_link(name: :a)
    {:ok, _} = Finch.start_link(name: :b)

    req = Finch.build(:get, "http://localhost:4000")

    Benchee.run(
      %{
        "a" => fn ->
          fun = fn
            {:status, value}, {_, headers, body, trailers} ->
              {:cont, {value, headers, body, trailers}}

            {:headers, value}, {status, headers, body, trailers} ->
              {:cont, {status, headers ++ value, body, trailers}}

            {:data, value}, {status, headers, body, trailers} ->
              {:cont, {status, headers, [value | body], trailers}}

            {:trailers, value}, {status, headers, body, trailers} ->
              {:cont, {status, headers, body, trailers ++ value}}
          end

          {:ok, {200, _headers, body, _trailers}} =
            Finch.stream_while(req, :a, {nil, [], [], []}, fun)

          body |> Enum.reverse() |> IO.iodata_to_binary() |> byte_size()
        end,
        "b" => fn ->
          fun = fn
            {:status, value}, {_, headers, body, trailers} ->
              {:cont, {value, headers, body, trailers}}

            {:headers, value}, {status, headers, body, trailers} ->
              {:cont, {status, headers ++ value, body, trailers}}

            {:data, value}, {status, headers, body, trailers} ->
              {:cont, {status, headers, [body | value], trailers}}

            {:trailers, value}, {status, headers, body, trailers} ->
              {:cont, {status, headers, body, trailers ++ value}}
          end

          {:ok, {200, _headers, body, _trailers}} =
            Finch.stream_while(req, :b, {nil, [], [], []}, fun)

          body |> IO.iodata_to_binary() |> byte_size()
        end
      },
      time: 10,
      memory_time: 2
    )

Results:

    12:00:04.273 [info] Running #Function<0.20032135 in file:bench.exs> with Bandit 1.5.3 at 0.0.0.0:4000 (http)
    Operating System: macOS
    CPU Information: Apple M2
    Number of Available Cores: 8
    Available memory: 24 GB
    Elixir 1.18.0-dev
    Erlang 27.0.1
    JIT enabled: true

    Benchmark suite executing with the following configuration:
    warmup: 2 s
    time: 10 s
    memory time: 2 s
    reduction time: 0 ns
    parallel: 1
    inputs: none specified
    Estimated total run time: 28 s

    Benchmarking a ...
    Benchmarking b ...
    Calculating statistics...
    Formatting results...

    Name           ips        average  deviation         median         99th %
    a            95.77       10.44 ms     ±2.19%       10.43 ms       11.21 ms
    b            95.43       10.48 ms     ±2.02%       10.46 ms       11.13 ms

    Comparison:
    a            95.77
    b            95.43 - 1.00x slower +0.0377 ms

    Memory usage statistics:

    Name         average  deviation         median         99th %
    a            2.24 MB     ±0.50%        2.24 MB        2.25 MB
    b            2.23 MB     ±0.35%        2.23 MB        2.24 MB

    Comparison:
    a            2.24 MB
    b            2.23 MB - 1.00x memory usage -0.01002 MB
@sneako
Copy link
Owner

sneako commented Aug 5, 2024

Great call, thanks Wojtek!

@sneako sneako merged commit 58001fb into sneako:main Aug 5, 2024
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants