Skip to content
New issue

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

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

Already on GitHub? # 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
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants