Skip to content

Optimize Calendar.ISO microsecond-to-time conversion - #15920

Merged
josevalim merged 1 commit into
elixir-lang:mainfrom
preciz:perf/calendar-joffe-6
Sep 20, 2026
Merged

josevalim merged 1 commit into
elixir-lang:mainfrom
preciz:perf/calendar-joffe-6

Conversation

@preciz

@preciz preciz commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

Assisted-by: Codex:GPT-6

~1.5x faster and uses less memory.

https://www.benjoffe.com/fast-time-of-day

Use bounded reciprocal multiplication to calculate minutes and total seconds
within an hour, recovering seconds and microseconds with bit masking and
subtraction. Guarding and inlining the conversion helper enables OTP to infer
integer bounds, avoid bignum overflow checks, and eliminate intermediate tuple
allocations.

Bench:

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

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

for {ref, module} <- [{"HEAD^", 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

Benchee.run(
  %{"Old" => &Old.time_from_day_fraction/1, "New" => &New.time_from_day_fraction/1},
  inputs: %{
    "midnight, zero fast path" => {0, 86_400_000_000},
    "12:34:56.123456, microseconds" => {45_296_123_456, 86_400_000_000},
    "before minute boundary" => {59_999_999, 86_400_000_000},
    "before hour boundary" => {3_599_999_999, 86_400_000_000},
    "last microsecond of day" => {86_399_999_999, 86_400_000_000},
    "scaled fraction, one seventh of day" => {1, 7}
  },
  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: 12:34:56.123456, microseconds, before hour boundary, before minute boundary, last microsecond of day, midnight, zero fast path, scaled fraction, one seventh of day
Estimated total run time: 1 min
Excluding outliers: false

##### With input 12:34:56.123456, microseconds #####
Name           ips        average  deviation         median         99th %
New        21.06 M       47.48 ns  ±7919.28%          40 ns          60 ns
Old        15.09 M       66.28 ns  ±8700.78%          50 ns          70 ns

Comparison: 
New        21.06 M
Old        15.09 M - 1.40x slower +18.80 ns

Memory usage statistics:

Name    Memory usage
New             88 B
Old            136 B - 1.55x memory usage +48 B

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

##### With input before hour boundary #####
Name           ips        average  deviation         median         99th %
New        20.89 M       47.88 ns  ±7726.98%          40 ns          60 ns
Old        15.16 M       65.97 ns  ±8280.82%          50 ns          70 ns

Comparison: 
New        20.89 M
Old        15.16 M - 1.38x slower +18.09 ns

Memory usage statistics:

Name    Memory usage
New             88 B
Old            136 B - 1.55x memory usage +48 B

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

##### With input before minute boundary #####
Name           ips        average  deviation         median         99th %
New        20.14 M       49.65 ns  ±7015.06%          40 ns          51 ns
Old        15.12 M       66.12 ns  ±8253.61%          50 ns          70 ns

Comparison: 
New        20.14 M
Old        15.12 M - 1.33x slower +16.47 ns

Memory usage statistics:

Name    Memory usage
New             88 B
Old            136 B - 1.55x memory usage +48 B

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

##### With input last microsecond of day #####
Name           ips        average  deviation         median         99th %
New        19.97 M       50.07 ns  ±7282.67%          40 ns          51 ns
Old        14.86 M       67.29 ns  ±8374.78%          50 ns          80 ns

Comparison: 
New        19.97 M
Old        14.86 M - 1.34x slower +17.22 ns

Memory usage statistics:

Name    Memory usage
New             88 B
Old            136 B - 1.55x memory usage +48 B

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

##### With input midnight, zero fast path #####
Name           ips        average  deviation         median         99th %
Old        42.08 M       23.76 ns   ±862.06%          20 ns          31 ns
New        41.78 M       23.94 ns   ±812.71%          20 ns          31 ns

Comparison: 
Old        42.08 M
New        41.78 M - 1.01x slower +0.171 ns

Memory usage statistics:

Name    Memory usage
Old              0 B
New              0 B - 1.00x memory usage +0 B

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

##### With input scaled fraction, one seventh of day #####
Name           ips        average  deviation         median         99th %
New        18.83 M       53.10 ns  ±6709.29%          40 ns          60 ns
Old        14.15 M       70.69 ns  ±7452.01%          60 ns          81 ns

Comparison: 
New        18.83 M
Old        14.15 M - 1.33x slower +17.59 ns

Memory usage statistics:

Name    Memory usage
New             88 B
Old            136 B - 1.55x memory usage +48 B

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

Use bounded reciprocal multiplication to calculate minutes and total seconds
within an hour, recovering seconds and microseconds with bit masking and
subtraction. Guarding and inlining the conversion helper enables OTP to infer
integer bounds, avoid bignum overflow checks, and eliminate intermediate tuple
allocations.

Assisted-by: Codex:GPT-6
@josevalim
josevalim merged commit 291cdfd into elixir-lang:main Sep 20, 2026
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