Skip to content

Commit a11ab09

Browse files
gh-148085: datetime cache time module lookups (#148088)
Co-authored-by: Stan Ulbrych <stan@python.org>
1 parent d915492 commit a11ab09

2 files changed

Lines changed: 41 additions & 25 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Speed up :meth:`~datetime.date.timetuple` and :meth:`~datetime.date.strftime`
2+
by caching the ``time.struct_time`` and ``time.strftime`` lookups, and
3+
:meth:`~datetime.datetime.today` by reading the clock directly instead of
4+
calling :func:`time.time`. Patching :mod:`time` after importing
5+
:mod:`datetime` no longer affects these methods. Patch by Maurycy
6+
Pawłowski-Wieroński.

Modules/_datetimemodule.c

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ typedef struct {
5252

5353
/* The interned Unix epoch datetime instance */
5454
PyObject *epoch;
55+
56+
PyObject *time_struct_time;
57+
PyObject *time_strftime;
5558
} datetime_state;
5659

5760
/* The module has a fixed number of static objects, due to being exposed
@@ -1892,10 +1895,12 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple,
18921895
assert(object && format && timetuple);
18931896
assert(PyUnicode_Check(format));
18941897

1895-
PyObject *strftime = PyImport_ImportModuleAttrString("time", "strftime");
1896-
if (strftime == NULL) {
1898+
PyObject *current_mod = NULL;
1899+
datetime_state *st = GET_CURRENT_STATE(current_mod);
1900+
if (st == NULL) {
18971901
return NULL;
18981902
}
1903+
PyObject *strftime = st->time_strftime;
18991904

19001905
/* Scan the input format, looking for %z/%Z/%f escapes, building
19011906
* a new format. Since computing the replacements for those codes
@@ -2055,7 +2060,7 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple,
20552060
Py_XDECREF(zreplacement);
20562061
Py_XDECREF(colonzreplacement);
20572062
Py_XDECREF(Zreplacement);
2058-
Py_XDECREF(strftime);
2063+
RELEASE_CURRENT_STATE(st, current_mod);
20592064
return result;
20602065

20612066
Error:
@@ -2068,41 +2073,26 @@ wrap_strftime(PyObject *object, PyObject *format, PyObject *timetuple,
20682073
* from C. Perhaps they should be.
20692074
*/
20702075

2071-
/* Call time.time() and return its result (a Python float). */
2072-
static PyObject *
2073-
time_time(void)
2074-
{
2075-
PyObject *result = NULL;
2076-
PyObject *time = PyImport_ImportModuleAttrString("time", "time");
2077-
2078-
if (time != NULL) {
2079-
result = PyObject_CallNoArgs(time);
2080-
Py_DECREF(time);
2081-
}
2082-
return result;
2083-
}
2084-
20852076
/* Build a time.struct_time. The weekday and day number are automatically
20862077
* computed from the y,m,d args.
20872078
*/
20882079
static PyObject *
20892080
build_struct_time(int y, int m, int d, int hh, int mm, int ss, int dstflag)
20902081
{
2091-
PyObject *struct_time;
2092-
PyObject *result;
2093-
2094-
struct_time = PyImport_ImportModuleAttrString("time", "struct_time");
2095-
if (struct_time == NULL) {
2082+
PyObject *current_mod = NULL;
2083+
datetime_state *st = GET_CURRENT_STATE(current_mod);
2084+
if (st == NULL) {
20962085
return NULL;
20972086
}
20982087

2099-
result = PyObject_CallFunction(struct_time, "((iiiiiiiii))",
2088+
PyObject *result = PyObject_CallFunction(st->time_struct_time,
2089+
"((iiiiiiiii))",
21002090
y, m, d,
21012091
hh, mm, ss,
21022092
weekday(y, m, d),
21032093
days_before_month(y, m) + d,
21042094
dstflag);
2105-
Py_DECREF(struct_time);
2095+
RELEASE_CURRENT_STATE(st, current_mod);
21062096
return result;
21072097
}
21082098

@@ -3337,7 +3327,11 @@ datetime_date_today_impl(PyTypeObject *type)
33373327
type);
33383328
}
33393329

3340-
PyObject *time = time_time();
3330+
PyTime_t ts;
3331+
if (PyTime_Time(&ts) < 0) {
3332+
return NULL;
3333+
}
3334+
PyObject *time = PyFloat_FromDouble(PyTime_AsSecondsDouble(ts));
33413335
if (time == NULL) {
33423336
return NULL;
33433337
}
@@ -7449,6 +7443,8 @@ init_state(datetime_state *st, PyObject *module, PyObject *old_module)
74497443
.us_per_week = Py_NewRef(st_old->us_per_week),
74507444
.seconds_per_day = Py_NewRef(st_old->seconds_per_day),
74517445
.epoch = Py_NewRef(st_old->epoch),
7446+
.time_struct_time = Py_NewRef(st_old->time_struct_time),
7447+
.time_strftime = Py_NewRef(st_old->time_strftime),
74527448
};
74537449
return 0;
74547450
}
@@ -7493,6 +7489,15 @@ init_state(datetime_state *st, PyObject *module, PyObject *old_module)
74937489
return -1;
74947490
}
74957491

7492+
st->time_struct_time = PyImport_ImportModuleAttrString("time", "struct_time");
7493+
if (st->time_struct_time == NULL) {
7494+
return -1;
7495+
}
7496+
st->time_strftime = PyImport_ImportModuleAttrString("time", "strftime");
7497+
if (st->time_strftime == NULL) {
7498+
return -1;
7499+
}
7500+
74967501
return 0;
74977502
}
74987503

@@ -7502,6 +7507,9 @@ traverse_state(datetime_state *st, visitproc visit, void *arg)
75027507
/* heap types */
75037508
Py_VISIT(st->isocalendar_date_type);
75047509

7510+
Py_VISIT(st->time_struct_time);
7511+
Py_VISIT(st->time_strftime);
7512+
75057513
return 0;
75067514
}
75077515

@@ -7517,6 +7525,8 @@ clear_state(datetime_state *st)
75177525
Py_CLEAR(st->us_per_week);
75187526
Py_CLEAR(st->seconds_per_day);
75197527
Py_CLEAR(st->epoch);
7528+
Py_CLEAR(st->time_struct_time);
7529+
Py_CLEAR(st->time_strftime);
75207530
return 0;
75217531
}
75227532

0 commit comments

Comments
 (0)