Skip to content

Optimize Cookies.encode by avoiding string interpolation#1313

Closed
preciz wants to merge 1 commit into
elixir-plug:mainfrom
preciz:optimization18
Closed

Optimize Cookies.encode by avoiding string interpolation#1313
preciz wants to merge 1 commit into
elixir-plug:mainfrom
preciz:optimization18

Conversation

@preciz

@preciz preciz commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Assited by: Antigravity CLI : Gemini Flash 3.5

~1.2x-1.7x speedup and same memory usage.

Bench:

Mix.install([
  {:benchee, "~> 1.3"}
])

defmodule OriginalCookieEncoder do
  def encode(key, opts \\ %{}) when is_map(opts) do
    value = Map.get(opts, :value)
    path = Map.get(opts, :path, "/")

    IO.iodata_to_binary([
      "#{key}=#{value}; path=#{path}",
      emit_if(opts[:domain], &["; domain=", &1]),
      emit_if(opts[:max_age], &encode_max_age(&1, opts)),
      emit_if(Map.get(opts, :secure, false), "; secure"),
      emit_if(Map.get(opts, :http_only, true), "; HttpOnly"),
      emit_if(Map.get(opts, :same_site, nil), &encode_same_site/1),
      emit_if(opts[:extra], &["; ", &1])
    ])
  end

  defp encode_max_age(max_age, opts) do
    time = Map.get(opts, :universal_time) || :calendar.universal_time()
    time = add_seconds(time, max_age)
    ["; expires=", rfc2822(time), "; max-age=", Integer.to_string(max_age)]
  end

  defp encode_same_site(value) when is_binary(value), do: "; SameSite=#{value}"

  defp emit_if(value, fun_or_string) do
    cond do
      !value ->
        []

      is_function(fun_or_string) ->
        fun_or_string.(value)

      is_binary(fun_or_string) ->
        fun_or_string
    end
  end

  defp pad(number) when number in 0..9, do: <<?0, ?0 + number>>
  defp pad(number), do: Integer.to_string(number)

  defp rfc2822({{year, month, day} = date, {hour, minute, second}}) do
    [
      weekday_name(:calendar.day_of_the_week(date)),
      ?,,
      ?\s,
      pad(day),
      ?\s,
      month_name(month),
      ?\s,
      Integer.to_string(year),
      ?\s,
      pad(hour),
      ?:,
      pad(minute),
      ?:,
      pad(second),
      " GMT"
    ]
  end

  defp weekday_name(1), do: "Mon"
  defp weekday_name(2), do: "Tue"
  defp weekday_name(3), do: "Wed"
  defp weekday_name(4), do: "Thu"
  defp weekday_name(5), do: "Fri"
  defp weekday_name(6), do: "Sat"
  defp weekday_name(7), do: "Sun"

  defp month_name(1), do: "Jan"
  defp month_name(2), do: "Feb"
  defp month_name(3), do: "Mar"
  defp month_name(4), do: "Apr"
  defp month_name(5), do: "May"
  defp month_name(6), do: "Jun"
  defp month_name(7), do: "Jul"
  defp month_name(8), do: "Aug"
  defp month_name(9), do: "Sep"
  defp month_name(10), do: "Oct"
  defp month_name(11), do: "Nov"
  defp month_name(12), do: "Dec"

  defp add_seconds(time, seconds_to_add) do
    time_seconds = :calendar.datetime_to_gregorian_seconds(time)
    :calendar.gregorian_seconds_to_datetime(time_seconds + seconds_to_add)
  end
end

defmodule OptimizedCookieEncoder do
  def encode(key, opts \\ %{}) when is_map(opts) do
    value = Map.get(opts, :value) || ""
    path = Map.get(opts, :path, "/")

    IO.iodata_to_binary([
      key, ?=, value, "; path=", path,
      emit_if(opts[:domain], &["; domain=", &1]),
      emit_if(opts[:max_age], &encode_max_age(&1, opts)),
      emit_if(Map.get(opts, :secure, false), "; secure"),
      emit_if(Map.get(opts, :http_only, true), "; HttpOnly"),
      emit_if(Map.get(opts, :same_site, nil), &encode_same_site/1),
      emit_if(opts[:extra], &["; ", &1])
    ])
  end

  defp encode_max_age(max_age, opts) do
    time = Map.get(opts, :universal_time) || :calendar.universal_time()
    time = add_seconds(time, max_age)
    ["; expires=", rfc2822(time), "; max-age=", Integer.to_string(max_age)]
  end

  defp encode_same_site(value) when is_binary(value), do: "; SameSite=#{value}"

  defp emit_if(value, fun_or_string) do
    cond do
      !value ->
        []

      is_function(fun_or_string) ->
        fun_or_string.(value)

      is_binary(fun_or_string) ->
        fun_or_string
    end
  end

  defp pad(number) when number in 0..9, do: <<?0, ?0 + number>>
  defp pad(number), do: Integer.to_string(number)

  defp rfc2822({{year, month, day} = date, {hour, minute, second}}) do
    [
      weekday_name(:calendar.day_of_the_week(date)),
      ?,,
      ?\s,
      pad(day),
      ?\s,
      month_name(month),
      ?\s,
      Integer.to_string(year),
      ?\s,
      pad(hour),
      ?:,
      pad(minute),
      ?:,
      pad(second),
      " GMT"
    ]
  end

  defp weekday_name(1), do: "Mon"
  defp weekday_name(2), do: "Tue"
  defp weekday_name(3), do: "Wed"
  defp weekday_name(4), do: "Thu"
  defp weekday_name(5), do: "Fri"
  defp weekday_name(6), do: "Sat"
  defp weekday_name(7), do: "Sun"

  defp month_name(1), do: "Jan"
  defp month_name(2), do: "Feb"
  defp month_name(3), do: "Mar"
  defp month_name(4), do: "Apr"
  defp month_name(5), do: "May"
  defp month_name(6), do: "Jun"
  defp month_name(7), do: "Jul"
  defp month_name(8), do: "Aug"
  defp month_name(9), do: "Sep"
  defp month_name(10), do: "Oct"
  defp month_name(11), do: "Nov"
  defp month_name(12), do: "Dec"

  defp add_seconds(time, seconds_to_add) do
    time_seconds = :calendar.datetime_to_gregorian_seconds(time)
    :calendar.gregorian_seconds_to_datetime(time_seconds + seconds_to_add)
  end
end

inputs = %{
  "Simple cookie" => %{key: "session_id", opts: %{value: "xyz123"}},
  "Nil cookie" => %{key: "session_id", opts: %{}},
  "Complex cookie" => %{key: "session_id", opts: %{value: "xyz123", secure: true, http_only: true, max_age: 3600, same_site: "Lax"}}
}

Benchee.run(
  %{
    "Original (Unoptimized)" => fn %{key: key, opts: opts} -> OriginalCookieEncoder.encode(key, opts) end,
    "Optimized (New)" => fn %{key: key, opts: opts} -> OptimizedCookieEncoder.encode(key, opts) end
  },
  inputs: inputs,
  time: 2,
  warmup: 2,
  memory_time: 1
)

Results on noisy system:

Operating System: Linux
CPU Information: AMD Ryzen 7 8845HS w
Number of Available Cores: 16
Available memory: 54.72 GB
Elixir 1.20.0
Erlang 29.0.1
JIT enabled: true

Benchmark suite executing with the following configuration:
warmup: 2 s
time: 2 s
memory time: 1 s
reduction time: 0 ns
parallel: 1
inputs: Complex cookie, Nil cookie, Simple cookie
Estimated total run time: 30 s
Excluding outliers: false

##### With input Complex cookie #####
Name                             ips        average  deviation         median         99th %
Optimized (New)               1.67 M      600.41 ns  ±1145.56%         501 ns        1373 ns
Original (Unoptimized)        1.40 M      715.00 ns   ±897.61%         601 ns        1502 ns

Comparison:
Optimized (New)               1.67 M
Original (Unoptimized)        1.40 M - 1.19x slower +114.59 ns

Memory usage statistics:

Name                      Memory usage
Optimized (New)                  904 B
Original (Unoptimized)           904 B - 1.00x memory usage +0 B

**All measurements for memory usage were the same**

##### With input Nil cookie #####
Name                             ips        average  deviation         median         99th %
Optimized (New)               6.52 M      153.40 ns  ±5017.02%         120 ns         251 ns
Original (Unoptimized)        3.72 M      268.52 ns  ±2892.86%         181 ns         441 ns

Comparison:
Optimized (New)               6.52 M
Original (Unoptimized)        3.72 M - 1.75x slower +115.12 ns

Memory usage statistics:

Name                      Memory usage
Optimized (New)                  248 B
Original (Unoptimized)           248 B - 1.00x memory usage +0 B

**All measurements for memory usage were the same**

##### With input Simple cookie #####
Name                             ips        average  deviation         median         99th %
Optimized (New)               6.85 M      145.92 ns  ±3391.17%         120 ns         260 ns
Original (Unoptimized)        3.71 M      269.56 ns  ±3417.04%         170 ns        1162 ns

Comparison:
Optimized (New)               6.85 M
Original (Unoptimized)        3.71 M - 1.85x slower +123.64 ns

Memory usage statistics:

Name                      Memory usage
Optimized (New)                  256 B
Original (Unoptimized)           256 B - 1.00x memory usage +0 B

**All measurements for memory usage were the same**

@preciz

preciz commented Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

I'm closing this and reopen a better Pull Request on this same code part

@preciz preciz closed this Jun 15, 2026
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.

1 participant