Skip to content

Avoid repeated conversions when shifting Calendar.ISO datetimes - #15924

Merged
josevalim merged 2 commits into
elixir-lang:mainfrom
preciz:perf/calendar-joffe-7
Sep 21, 2026
Merged

josevalim merged 2 commits into
elixir-lang:mainfrom
preciz:perf/calendar-joffe-7

Conversation

@preciz

@preciz preciz commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

Assisted-by: Codex:GPT-6

~1.5x faster and uses less memory.

Combine seconds and microseconds into one shift after applying months,
preserving existing precision semantics. Inline duration arithmetic and
set precision upfront to tail-call the shift helper.

Bench:

# Run with: bin/elixir bench.exs
Mix.install([:benchee])

path = "lib/elixir/lib/calendar/iso.ex"

for {ref, module} <- [{"main", Old}, {"HEAD", New}] do
  {source, 0} = System.cmd("git", ["show", "#{ref}:#{path}"], cd: __DIR__)

  source
  |> String.replace("defmodule Calendar.ISO do", "defmodule #{inspect(module)} do")
  |> Code.compile_string(path)
end

inputs = %{
  "months only, clamp to leap day" => [month: 1],
  "seconds only, cross day" => [second: 86_401],
  "microseconds only" => [microsecond: {500_001, 6}],
  "seconds and microseconds" => [second: 86_401, microsecond: {500_001, 6}],
  "all units" => [
    year: 1,
    month: 1,
    week: 2,
    day: 3,
    hour: 4,
    minute: 5,
    second: 6,
    microsecond: {7, 6}
  ]
}

Benchee.run(
  %{
    "main" => &Old.shift_naive_datetime(2024, 1, 31, 23, 59, 59, {120_000, 3}, &1),
    "HEAD" => &New.shift_naive_datetime(2024, 1, 31, 23, 59, 59, {120_000, 3}, &1)
  },
  inputs: Map.new(inputs, fn {name, units} -> {name, Duration.new!(units)} end),
  pre_check: :all_same,
  warmup: 1,
  time: 3,
  memory_time: 1
)

Results:

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

Benchmark suite executing with the following configuration:
warmup: 1 s
time: 3 s
memory time: 1 s
reduction time: 0 ns
parallel: 1
inputs: all units, microseconds only, months only, clamp to leap day, seconds and microseconds, seconds only, cross day
Estimated total run time: 50 s
Excluding outliers: false

##### With input all units #####
Name           ips        average  deviation         median         99th %
HEAD        4.46 M      224.11 ns  ±2511.99%         200 ns         370 ns
main        2.31 M      432.57 ns  ±1539.64%         400 ns         591 ns

Comparison: 
HEAD        4.46 M
main        2.31 M - 1.93x slower +208.46 ns

Memory usage statistics:

Name    Memory usage
HEAD         0.53 KB
main         1.05 KB - 1.97x memory usage +0.52 KB

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

##### With input microseconds only #####
Name           ips        average  deviation         median         99th %
HEAD        4.67 M      214.06 ns  ±3867.57%         180 ns         341 ns
main        3.91 M      255.70 ns  ±4816.90%         200 ns         371 ns

Comparison: 
HEAD        4.67 M
main        3.91 M - 1.19x slower +41.64 ns

Memory usage statistics:

Name    Memory usage
HEAD           512 B
main           552 B - 1.08x memory usage +40 B

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

##### With input months only, clamp to leap day #####
Name           ips        average  deviation         median         99th %
HEAD       11.81 M       84.67 ns  ±7808.55%          60 ns         100 ns
main        6.96 M      143.75 ns  ±5285.02%         110 ns         250 ns

Comparison: 
HEAD       11.81 M
main        6.96 M - 1.70x slower +59.08 ns

Memory usage statistics:

Name    Memory usage
HEAD           152 B
main           312 B - 2.05x memory usage +160 B

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

##### With input seconds and microseconds #####
Name           ips        average  deviation         median         99th %
HEAD        4.89 M      204.67 ns  ±3842.69%         170 ns         330 ns
main        2.45 M      408.15 ns  ±3616.15%         331 ns         711 ns

Comparison: 
HEAD        4.89 M
main        2.45 M - 1.99x slower +203.48 ns

Memory usage statistics:

Name    Memory usage
HEAD           512 B
main           944 B - 1.84x memory usage +432 B

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

##### With input seconds only, cross day #####
Name           ips        average  deviation         median         99th %
HEAD        4.83 M      206.98 ns  ±3756.77%         170 ns         330 ns
main        3.90 M      256.49 ns  ±4007.68%         201 ns         391 ns

Comparison: 
HEAD        4.83 M
main        3.90 M - 1.24x slower +49.50 ns

Memory usage statistics:

Name    Memory usage
HEAD           536 B
main           576 B - 1.07x memory usage +40 B

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

Combine seconds and microseconds into one shift after applying months,
preserving existing precision semantics. Inline duration arithmetic and
set precision upfront to tail-call the shift helper.

Assisted-by: Codex:GPT-6
]
{
year * 12 + month,
(((week * @days_per_week + day) * 24 + hour) * 60 + minute) * 60 + second,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nesting in parentheses here allows the compiler to better optimize

Comment thread lib/elixir/test/elixir/calendar/iso_test.exs
Remove redundant month-ordering coverage and retain precision loop tests.

Assisted-by: Codex:GPT-6
@josevalim

Copy link
Copy Markdown
Member

💚 💙 💜 💛 ❤️

@josevalim
josevalim merged commit c21db1f into elixir-lang:main Sep 21, 2026
15 checks passed
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