Skip to content

PERF: branchless calendar algorithms for datetime conversions#31126

Open
jbrockmendel wants to merge 3 commits intonumpy:mainfrom
jbrockmendel:perf-calendar
Open

PERF: branchless calendar algorithms for datetime conversions#31126
jbrockmendel wants to merge 3 commits intonumpy:mainfrom
jbrockmendel:perf-calendar

Conversation

@jbrockmendel
Copy link
Copy Markdown
Contributor

@jbrockmendel jbrockmendel commented Apr 2, 2026

Claude wrote this, i reviewed manually. As with my other PRs, my C-fu is weak.

This is explicitly modeled on pandas-dev/pandas#64662.

cc @seberg

Replace `get_datetimestruct_days` (ymd→days) with Hinnant's loop-free
public-domain algorithm and `set_datetimestruct_days` (days→ymd) with
Neri-Schneider's branchless Euclidean Affine Function algorithm.

Simplify `days_to_month_number` to delegate to the new
`set_datetimestruct_days` instead of duplicating the days→year→month
logic.

Mirrors pandas PR #64662.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ngoldbaum
Copy link
Copy Markdown
Member

Can we have a little more context why we want this? Speed, correctness, something else?

@jbrockmendel
Copy link
Copy Markdown
Contributor Author

The benefit is performance. I see a 5% speedup in ns->D conversion, 18% in ns->M.

@charris
Copy link
Copy Markdown
Member

charris commented Apr 4, 2026

close/reopen

@charris charris closed this Apr 4, 2026
@charris charris reopened this Apr 4, 2026
@ngoldbaum
Copy link
Copy Markdown
Member

Can we get some concrete benchmarks demonstrating the speedup? Can you also verify that our test coverage for these conversions is good? I'm a little nervous about completely changing the implementation of a poorly tested codepath.

If you know of an expert we can ping to review this, that might also help assuage concerns about correctness.

Test the Neri-Schneider fast-path boundaries, the fallback path
for extreme dates just outside that range, and typical dates
including leap year boundaries (1900, 2000).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ngoldbaum ngoldbaum added the triage review Issue/PR to be discussed at the next triage meeting label Apr 9, 2026
@ngoldbaum ngoldbaum added this to the 2.5.0 Release milestone Apr 9, 2026
@jbrockmendel
Copy link
Copy Markdown
Contributor Author

Can we get some concrete benchmarks demonstrating the speedup?

In [3]: arr = np.arange("2000-01-01", "2010-01-01", dtype="datetime64[s]").astype("datetime64[ns]")

In [4]: %timeit arr.astype("datetime64[M]")
3.55 s ± 25.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)  # <- main
2.95 s ± 58.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)  # <- PR

In [5]: %timeit arr.astype("datetime64[D]")
416 ms ± 99.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)  # <- main
350 ms ± 32.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)  # <- PR

Can you also verify that our test coverage for these conversions is good?

claude says "The existing 470 tests already cover pre-1970 dates, leap year boundaries, year 0/negatives, Y2038, and int64 limits"

I'm a little nervous about completely changing the implementation of a poorly tested codepath.

That's similar to the comment in pandas asking me to open this PR. The hope is that by keeping the implementations aligned we can benefit from each others' coverage.

FWIW my understanding is that the same algorithm is used in c++20's chrono implementation. We just can't use it directly because they don't support int64 years.

@ngoldbaum
Copy link
Copy Markdown
Member

I added a triage review label to make sure we talk about this at next week's triage meeting. Feel free to come, see the calendar.

FWIW my understanding is that the same algorithm is used in c++20's chrono implementation.

Oh nice, it might also help if you add a link to an implementation of this as a comment. Someone could also try comparing the two implementations.

@seberg
Copy link
Copy Markdown
Member

seberg commented Apr 10, 2026

Yeah, I think it probably makes sense to just follow pandas, it seems like the sources are trustworthy, although reviewing that it is identical code isn't all that easy (bots help a bit with confidence, I guess).
It seems the GCC version: https://gcc.gnu.org/git/?p=gcc.git;a=blob;f=libstdc%2B%2B-v3/include/std/chrono;h=10e868e5a036924177cf952a81f4bce10c497106;hb=HEAD#l1678
uses the same/very similar approach, but very slightly different constants (no full understanding here, but it seems related to the valid range of years).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

03 - Maintenance triage review Issue/PR to be discussed at the next triage meeting

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants