Skip to content

Optimize Duration.add, subtract, multiply, negate#15501

Merged
josevalim merged 1 commit into
elixir-lang:mainfrom
preciz:optimization18
Jun 17, 2026
Merged

Optimize Duration.add, subtract, multiply, negate#15501
josevalim merged 1 commit into
elixir-lang:mainfrom
preciz:optimization18

Conversation

@preciz

@preciz preciz commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Assisted-by: Antigravity CLI : Gemini Flash 3.5

~1.3x faster, uses less memory, it's only a tiny bit more code so I think it's a good balance

Bench:

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

defmodule OriginalDuration do
  def negate(%Duration{microsecond: {ms, p}} = duration) do
    %Duration{
      year: -duration.year,
      month: -duration.month,
      week: -duration.week,
      day: -duration.day,
      hour: -duration.hour,
      minute: -duration.minute,
      second: -duration.second,
      microsecond: {-ms, p}
    }
  end

  def add(%Duration{} = d1, %Duration{} = d2) do
    {m1, p1} = d1.microsecond
    {m2, p2} = d2.microsecond

    %Duration{
      year: d1.year + d2.year,
      month: d1.month + d2.month,
      week: d1.week + d2.week,
      day: d1.day + d2.day,
      hour: d1.hour + d2.hour,
      minute: d1.minute + d2.minute,
      second: d1.second + d2.second,
      microsecond: {m1 + m2, max(p1, p2)}
    }
  end

  def subtract(%Duration{} = d1, %Duration{} = d2) do
    {m1, p1} = d1.microsecond
    {m2, p2} = d2.microsecond

    %Duration{
      year: d1.year - d2.year,
      month: d1.month - d2.month,
      week: d1.week - d2.week,
      day: d1.day - d2.day,
      hour: d1.hour - d2.hour,
      minute: d1.minute - d2.minute,
      second: d1.second - d2.second,
      microsecond: {m1 - m2, max(p1, p2)}
    }
  end

  def multiply(%Duration{microsecond: {ms, p}} = duration, integer) when is_integer(integer) do
    %Duration{
      year: duration.year * integer,
      month: duration.month * integer,
      week: duration.week * integer,
      day: duration.day * integer,
      hour: duration.hour * integer,
      minute: duration.minute * integer,
      second: duration.second * integer,
      microsecond: {ms * integer, p}
    }
  end
end

defmodule OptimizedDuration do
  def negate(%Duration{microsecond: {ms, p}} = duration) do
    %{year: y, month: mo, week: w, day: d, hour: h, minute: mi, second: s} = duration

    %Duration{
      year: -y,
      month: -mo,
      week: -w,
      day: -d,
      hour: -h,
      minute: -mi,
      second: -s,
      microsecond: {-ms, p}
    }
  end

  def add(%Duration{microsecond: {ms1, p1}} = d1, %Duration{microsecond: {ms2, p2}} = d2) do
    %{year: y1, month: mo1, week: w1, day: day1, hour: h1, minute: mi1, second: s1} = d1
    %{year: y2, month: mo2, week: w2, day: day2, hour: h2, minute: mi2, second: s2} = d2

    %Duration{
      year: y1 + y2,
      month: mo1 + mo2,
      week: w1 + w2,
      day: day1 + day2,
      hour: h1 + h2,
      minute: mi1 + mi2,
      second: s1 + s2,
      microsecond: {ms1 + ms2, max(p1, p2)}
    }
  end

  def subtract(%Duration{microsecond: {ms1, p1}} = d1, %Duration{microsecond: {ms2, p2}} = d2) do
    %{year: y1, month: mo1, week: w1, day: day1, hour: h1, minute: mi1, second: s1} = d1
    %{year: y2, month: mo2, week: w2, day: day2, hour: h2, minute: mi2, second: s2} = d2

    %Duration{
      year: y1 - y2,
      month: mo1 - mo2,
      week: w1 - w2,
      day: day1 - day2,
      hour: h1 - h2,
      minute: mi1 - mi2,
      second: s1 - s2,
      microsecond: {ms1 - ms2, max(p1, p2)}
    }
  end

  def multiply(%Duration{microsecond: {ms, p}} = duration, integer) when is_integer(integer) do
    %{year: y, month: mo, week: w, day: d, hour: h, minute: mi, second: s} = duration

    %Duration{
      year: y * integer,
      month: mo * integer,
      week: w * integer,
      day: d * integer,
      hour: h * integer,
      minute: mi * integer,
      second: s * integer,
      microsecond: {ms * integer, p}
    }
  end
end

dur1 =
  Duration.new!(
    year: 1,
    month: 2,
    week: 3,
    day: 4,
    hour: 5,
    minute: 6,
    second: 7,
    microsecond: {8, 6}
  )

dur2 =
  Duration.new!(
    year: 10,
    month: 20,
    week: 30,
    day: 40,
    hour: 50,
    minute: 60,
    second: 70,
    microsecond: {80, 6}
  )

IO.puts("=== BENCHMARKING ADD ===")

Benchee.run(
  %{
    "original_add" => fn -> OriginalDuration.add(dur1, dur2) end,
    "optimized_add" => fn -> OptimizedDuration.add(dur1, dur2) end
  },
  memory_time: 2,
  pre_check: :all_same
)

IO.puts("=== BENCHMARKING SUBTRACT ===")

Benchee.run(
  %{
    "original_subtract" => fn -> OriginalDuration.subtract(dur1, dur2) end,
    "optimized_subtract" => fn -> OptimizedDuration.subtract(dur1, dur2) end
  },
  memory_time: 2,
  pre_check: :all_same
)

IO.puts("=== BENCHMARKING MULTIPLY ===")

Benchee.run(
  %{
    "original_multiply" => fn -> OriginalDuration.multiply(dur1, 3) end,
    "optimized_multiply" => fn -> OptimizedDuration.multiply(dur1, 3) end
  },
  memory_time: 2,
  pre_check: :all_same
)

IO.puts("=== BENCHMARKING NEGATE ===")

Benchee.run(
  %{
    "original_negate" => fn -> OriginalDuration.negate(dur1) end,
    "optimized_negate" => fn -> OptimizedDuration.negate(dur1) end
  },
  memory_time: 2,
  pre_check: :all_same
)

Bech 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.2
JIT enabled: true

=== BENCHMARKING ADD ===

Name                    ips        average  deviation         median         99th %
optimized_add       15.67 M       63.80 ns  ±9232.77%          50 ns          70 ns
original_add         7.42 M      134.71 ns  ±6145.52%         100 ns         180 ns

Comparison:
optimized_add       15.67 M
original_add         7.42 M - 2.11x slower +70.91 ns

Memory usage statistics:

Name             Memory usage
optimized_add           120 B
original_add            120 B - 1.00x memory usage +0 B

**All measurements for memory usage were the same**
=== BENCHMARKING SUBTRACT ===

Name                         ips        average  deviation         median         99th %
optimized_subtract       14.07 M       71.09 ns  ±9982.52%          50 ns          90 ns
original_subtract         6.97 M      143.54 ns  ±6131.29%         100 ns         190 ns

Comparison:
optimized_subtract       14.07 M
original_subtract         6.97 M - 2.02x slower +72.45 ns

Memory usage statistics:

Name                  Memory usage
optimized_subtract           120 B
original_subtract            120 B - 1.00x memory usage +0 B

**All measurements for memory usage were the same**
=== BENCHMARKING MULTIPLY ===

Name                         ips        average  deviation         median         99th %
optimized_multiply       16.17 M       61.86 ns  ±9834.49%          40 ns          70 ns
original_multiply         8.82 M      113.34 ns  ±7818.94%          70 ns         140 ns

Comparison:
optimized_multiply       16.17 M
original_multiply         8.82 M - 1.83x slower +51.48 ns

Memory usage statistics:

Name                  Memory usage
optimized_multiply           120 B
original_multiply            120 B - 1.00x memory usage +0 B

**All measurements for memory usage were the same**
=== BENCHMARKING NEGATE ===

Name                       ips        average  deviation         median         99th %
optimized_negate       15.86 M       63.04 ns  ±9891.18%          40 ns          70 ns
original_negate        10.50 M       95.21 ns  ±7577.30%          61 ns         101 ns

Comparison:
optimized_negate       15.86 M
original_negate        10.50 M - 1.51x slower +32.17 ns

Memory usage statistics:

Name                Memory usage
optimized_negate           120 B
original_negate            120 B - 1.00x memory usage +0 B

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

@josevalim josevalim merged commit 1e12c7d into elixir-lang:main Jun 17, 2026
14 of 15 checks passed
@josevalim

Copy link
Copy Markdown
Member

💚 💙 💜 💛 ❤️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants