-
-
Notifications
You must be signed in to change notification settings - Fork 34.4k
gh-148085: datetime cache time module lookups
#148088
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 5 commits
36c494b
48e9ea1
ae32bfd
7e7b739
cd247ca
2f3dbe0
d3944fe
33c64f2
ff27dc4
056cf08
e00aa62
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| Cache ``time.time``, ``time.struct_time``, and ``time.strftime`` imports, | ||
| speeding up :meth:`~datetime.date.timetuple` and | ||
| :meth:`~datetime.date.strftime` by up to 2x. Patch by Maurycy | ||
| Pawłowski-Wieroński. |
| Original file line number | Diff line number | Diff line change | |||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -52,6 +52,10 @@ typedef struct { | ||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||
| /* The interned Unix epoch datetime instance */ | |||||||||||||||||||||||||||||||||
| PyObject *epoch; | |||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||
| PyObject *time_time; | |||||||||||||||||||||||||||||||||
| PyObject *time_struct_time; | |||||||||||||||||||||||||||||||||
| PyObject *time_strftime; | |||||||||||||||||||||||||||||||||
| } datetime_state; | |||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||
| /* The module has a fixed number of static objects, due to being exposed | |||||||||||||||||||||||||||||||||
|
|
@@ -1879,10 +1883,19 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple, | ||||||||||||||||||||||||||||||||
| assert(object && format && timetuple); | |||||||||||||||||||||||||||||||||
| assert(PyUnicode_Check(format)); | |||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||
| PyObject *strftime = PyImport_ImportModuleAttrString("time", "strftime"); | |||||||||||||||||||||||||||||||||
| if (strftime == NULL) { | |||||||||||||||||||||||||||||||||
| PyObject *current_mod = NULL; | |||||||||||||||||||||||||||||||||
| datetime_state *st = GET_CURRENT_STATE(current_mod); | |||||||||||||||||||||||||||||||||
| if (st == NULL) { | |||||||||||||||||||||||||||||||||
| return NULL; | |||||||||||||||||||||||||||||||||
| } | |||||||||||||||||||||||||||||||||
| if (st->time_strftime == NULL) { | |||||||||||||||||||||||||||||||||
| st->time_strftime = PyImport_ImportModuleAttrString("time", "strftime"); | |||||||||||||||||||||||||||||||||
| if (st->time_strftime == NULL) { | |||||||||||||||||||||||||||||||||
| RELEASE_CURRENT_STATE(st, current_mod); | |||||||||||||||||||||||||||||||||
| return NULL; | |||||||||||||||||||||||||||||||||
| } | |||||||||||||||||||||||||||||||||
| } | |||||||||||||||||||||||||||||||||
| PyObject *strftime = st->time_strftime; | |||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||
| /* Scan the input format, looking for %z/%Z/%f escapes, building | |||||||||||||||||||||||||||||||||
| * a new format. Since computing the replacements for those codes | |||||||||||||||||||||||||||||||||
|
|
@@ -2042,7 +2055,7 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple, | ||||||||||||||||||||||||||||||||
| Py_XDECREF(zreplacement); | |||||||||||||||||||||||||||||||||
| Py_XDECREF(colonzreplacement); | |||||||||||||||||||||||||||||||||
| Py_XDECREF(Zreplacement); | |||||||||||||||||||||||||||||||||
| Py_XDECREF(strftime); | |||||||||||||||||||||||||||||||||
| RELEASE_CURRENT_STATE(st, current_mod); | |||||||||||||||||||||||||||||||||
| return result; | |||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||
| Error: | |||||||||||||||||||||||||||||||||
|
|
@@ -2059,13 +2072,20 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple, | ||||||||||||||||||||||||||||||||
| static PyObject * | |||||||||||||||||||||||||||||||||
| time_time(void) | |||||||||||||||||||||||||||||||||
| { | |||||||||||||||||||||||||||||||||
| PyObject *result = NULL; | |||||||||||||||||||||||||||||||||
| PyObject *time = PyImport_ImportModuleAttrString("time", "time"); | |||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||
| if (time != NULL) { | |||||||||||||||||||||||||||||||||
| result = PyObject_CallNoArgs(time); | |||||||||||||||||||||||||||||||||
| Py_DECREF(time); | |||||||||||||||||||||||||||||||||
| PyObject *current_mod = NULL; | |||||||||||||||||||||||||||||||||
| datetime_state *st = GET_CURRENT_STATE(current_mod); | |||||||||||||||||||||||||||||||||
| if (st == NULL) { | |||||||||||||||||||||||||||||||||
| return NULL; | |||||||||||||||||||||||||||||||||
| } | |||||||||||||||||||||||||||||||||
| if (st->time_time == NULL) { | |||||||||||||||||||||||||||||||||
| st->time_time = PyImport_ImportModuleAttrString("time", "time"); | |||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||
| Benchmark | main-fbdbea9 | datetime-cache-time-module-lookups-d3944fe |
|---|---|---|
| datetime.timetuple | 264 ns | 130 ns: 2.04x faster |
| datetime.strftime | 776 ns | 524 ns: 1.48x faster |
| date.timetuple | 263 ns | 129 ns: 2.04x faster |
| date.today | 78.4 ns | 76.0 ns: 1.03x faster |
| date_subclass.today | 306 ns | 149 ns: 2.05x faster |
| Geometric mean | (ref) | 1.67x faster |
date.today is expected: a fast hot path, but very good result in bypassing import in date_subclass.today
The downside is - as per @picnixz comment - that it now definitely breaks removing the module from sys.modules.
On the other hand, we don't seem to particularly care about monkey-patching:
Line 371 in fbdbea9
| st->codecs_encode = PyImport_ImportModuleAttrString("codecs", "encode"); |
Line 2562 in fbdbea9
| state->array_reconstructor = PyImport_ImportModuleAttrString( |
Line 2765 in fbdbea9
| state->_tzpath_find_tzfile = |
cpython/Modules/_asynciomodule.c
Line 4257 in fbdbea9
| state->asyncio_mod = PyImport_ImportModule("asyncio"); |
cpython/Modules/_decimal/_decimal.c
Line 3700 in fbdbea9
| state->PyDecimal = PyImport_ImportModuleAttrString("_pydecimal", "Decimal"); |
etc.
Uh oh!
There was an error while loading. Please reload this page.