Conversation
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.
pganssle
left a comment
There was a problem hiding this comment.
Few comments for the agent.
| } | ||
| /* January 1 of year 1 was a Monday. */ | ||
| int year = fields.year - 1; | ||
| int weekday = (365 * year + year / 4 - year / 100 + year / 400 + |
There was a problem hiding this comment.
This should be its own function somewhere. We must already have this, right?
|
|
||
| /* Locale-independent numeric strptime parsing. | ||
| * | ||
| * Return 1 for a complete numeric match, or 0 to use Lib/_strptime.py. |
There was a problem hiding this comment.
Best to explain a bit more what this is, like so:
This implements the most common parts of the strptime spec
in C to improve performance; if this fails callers should fall back
to_strptimefor a full parse.Returns 1 for a complete numeric match or 0 if fallback is required.
Parsing doesn't allocate or set exceptions; in particular, mismatches
may require regex backtracking, so the fallback should diagnose them.
I am also unclear: are there situations where the fallback can be skipped? For example:
datetime.strptime("2021-13-04", "%Y-%m-%d")This parses correctly but validates incorrectly. Parsing it again won't change that result.
I also imagine that for known formats you can also see that it parses correctly and know that the fallback won't fix it, like, ("", "%Y-%m-%d"), and I don't see why regex backtracking would be required. Maybe there's some other reason why the fallback should always be used to set the error conditions, but if so it should be clearer from the comment here.
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.
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.