Repository navigation
gh-158252: Add C implementation for strptime - #158253
Merged
Merged
Conversation
pganssle
commented
Sep 26, 2026
pganssle
left a comment
Member
Author
There was a problem hiding this comment.
Few comments for the agent.
Restrict the fast path to independent ASCII numeric fields and defer unusual formats and mismatches to the Python parser. This preserves regex backtracking, duplicate-directive errors, and current diagnostics for dates without a year. Use string lengths to preserve embedded NULs, validate calendar dates before returning time tuples, and preserve allocation failures.
The compatible accelerator spends more time building and unpacking Python objects and traversing wrappers than parsing fields. Keep those fields in C and use the existing datetime and timezone construction machinery. Move the numeric parser into _datetime and remove the separate extension. Exact datetime instances can avoid importing _strptime and acquiring its locale/cache lock. Subclasses, pure-Python datetime, and other entry points retain the Python path, including constructor arguments and diagnostics. Drop unused directive and date-normalization code. Parsing itself neither allocates nor sets exceptions; failure requests Python fallback. Errors from result construction propagate normally.
Move numeric field parsing into pytime so the time and datetime modules can use it without importing each other. Keep result construction in the caller and expose the day of year for struct_time construction.
Construct date, time, and struct_time results from the shared numeric fields. Preserve subclass constructors and Python fallback behavior, including leap seconds, discarded fields, and timezone metadata.
Reuse the datetime calendar arithmetic for struct_time construction. Document the numeric parser scope and why even known-invalid inputs use the Python parser for error messages and error precedence.
pganssle
force-pushed
the
c-strptime-optimization
branch
from
October 20, 2026 19:07
207ae59 to
510c622
Compare
pganssle
marked this pull request as ready for review
October 20, 2026 19:08
StanFromIreland
left a comment
Member
There was a problem hiding this comment.
A few comments, but overall looking really good :-)
Replace positional flags with a lookup table and named masks, so year and day checks do not depend on the order of a directive string. Use new_datetime_ex for the exact-type fast path and Py_ISSPACE for ASCII whitespace. Add tests covering Unicode whitespace in both the input and format across all four public entry points.
Keep strptime parsing separate from the clock and timestamp helpers in pytime.c. Update the Unix and Windows builds, including _freeze_module. The parser is unchanged in this commit, so the move can be reverted independently of the review fixes.
pganssle
requested review from
a team,
AA-Turner,
emmatyping,
ericsnowcurrently,
erlend-aasland and
itamaro
as code owners
October 21, 2026 18:32
Member
|
Please add this to Optimisations in the What's New. |
Documentation build overview
8 files changed ·
|
Py_ISSPACE excludes ASCII separators U+001C through U+001F, which the Python parser treats as whitespace. Consuming one as a literal can leave a space for the next numeric field, changing how adjacent fields split. Use Py_UNICODE_ISSPACE and add regression tests for all four public entry points. These cases returned different dates or times before the fix.
StanFromIreland
approved these changes
Oct 23, 2026
StanFromIreland
enabled auto-merge (squash)
October 23, 2026 14:49
Member
|
Sorry for the delay, let's get this in! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #158252.
This adds a shared C parser for common numeric formats used by
datetime.datetime.strptime,datetime.date.strptime,datetime.time.strptime, andtime.strptime. Each caller constructs its result directly from the parsed fields, avoiding the Python parser's regex cache, locale checks, and intermediate objects. It builds on @StanFromIreland's initial implementation.The fast path handles ASCII numeric fields, including fractional seconds and a trailing
%zwith minute-resolution offsets orZ. Other inputs use the existing Python parser, preserving backtracking, warnings, and error messages. Subclasses also use the Python path so their constructor arguments don't change. Each caller retains its existing handling of discarded fields, leap seconds, and timezone information. The pure-Python implementations are unchanged.Benchmarks compare the PR base,
de0d9763682, with207ae592412, using matching GCC 16.2.1 release builds (-O3, without PGO/LTO) andLC_TIME=C. All four methods parse the same 32 inputs for each format. These are warmed mean times per public call, including method lookup and loop overhead. The methods construct different results, so their timings aren't identical.For
datetime.datetime.strptime:%Y-%m-%d%Y-%m-%d %H:%M:%S%Y-%m-%d %H:%M:%S.%f%Y%m%d%H%M%S%Y-%m-%dT%H:%M:%S%z(UTC)%Y-%m-%dT%H:%M:%S.%f%z(+0530)%H:%M:%S%H:%M:%S.%f%z(+0530)%Y-%m-%d %H:%M:%S+ rotating suffix%d %B %Y %H:%M:%S‡%Y-%m-%d %I:%M:%S %p‡%d %B %Y %H:%M:%S+ rotating suffix ‡The rotating cases append literal suffixes
/0through/31to the input and format, exceeding the Python parser's five-entry regex cache. ‡ These formats use the Python fallback. A dash (—) in the speedup column meanspyperfdidn't find a statistically significant difference; it doesn't identify the parsing path.datetime.date.strptime benchmarks
%Y-%m-%d%Y-%m-%d %H:%M:%S%Y-%m-%d %H:%M:%S.%f%Y%m%d%H%M%S%Y-%m-%dT%H:%M:%S%z(UTC)%Y-%m-%dT%H:%M:%S.%f%z(+0530)%H:%M:%S%H:%M:%S.%f%z(+0530)%Y-%m-%d %H:%M:%S+ rotating suffix%d %B %Y %H:%M:%S‡%Y-%m-%d %I:%M:%S %p‡%d %B %Y %H:%M:%S+ rotating suffix ‡datetime.time.strptime benchmarks
%Y-%m-%d%Y-%m-%d %H:%M:%S%Y-%m-%d %H:%M:%S.%f%Y%m%d%H%M%S%Y-%m-%dT%H:%M:%S%z(UTC)%Y-%m-%dT%H:%M:%S.%f%z(+0530)%H:%M:%S%H:%M:%S.%f%z(+0530)%Y-%m-%d %H:%M:%S+ rotating suffix%d %B %Y %H:%M:%S‡%Y-%m-%d %I:%M:%S %p‡%d %B %Y %H:%M:%S+ rotating suffix ‡time.strptime benchmarks
%Y-%m-%d%Y-%m-%d %H:%M:%S%Y-%m-%d %H:%M:%S.%f%Y%m%d%H%M%S%Y-%m-%dT%H:%M:%S%z(UTC)%Y-%m-%dT%H:%M:%S.%f%z(+0530)%H:%M:%S%H:%M:%S.%f%z(+0530)%Y-%m-%d %H:%M:%S+ rotating suffix%d %B %Y %H:%M:%S‡%Y-%m-%d %I:%M:%S %p‡%d %B %Y %H:%M:%S+ rotating suffix ‡The initial runs were noisy, so all fallback and UTC rows use longer, closely paired repeats. Eight of the twelve fallback comparisons weren't significant. The repeats measured
time.strptimewith AM/PM about 4% slower anddate.strptimewith rotating month-name formats about 1.3% slower; two other fallback comparisons were about 3% faster. Small fallback differences remain sensitive to measurement variability.The three targeted test modules (
test_datetime,test_strptime, andtest_time) pass. Differential checks found no differences in 74,708 public API comparisons, 18,677 additional metadata/error/warning comparisons, and 119,988 calendar checks against the pure-Python implementation.Benchmark method and reproducer
Workers were pinned to one CPU, with builds and correctness tests completed before timing. The full suite ran in base/PR/PR/base order, with two worker processes per run, four values per worker, two warmups, and a 50 ms minimum sample time. UTC and fallback cases were repeated individually in the same order, with six values, three warmups, and a 100 ms minimum. The tables use those repeats for UTC and fallback rows and the full-suite runs for the remaining rows. All 1,536 expected results, including
struct_timetimezone metadata, are checked before timing.Build
de0d9763682and207ae592412in separate checkouts with GCC 16.2.1 and the same options:./configure CFLAGS='-O3 -g -fno-omit-frame-pointer' --without-ensurepip make -j4Save the script below as
bench_strptime.py. Set the interpreter paths and choose an available CPU forBENCH_CPU. The first group measures all 48 combinations; the remaining groups repeat UTC and fallback cases with closer pairing and longer samples.