Skip to content

Commit 36b9549

Browse files
committed
gh-112919: Speed-up datetime, date and time.replace()
Use argument clinic and call new_* functions directly. This speeds up these functions 6x to 7.5x when calling with keyword arguments. Before: $ python -m timeit -s "from datetime import datetime; dt = datetime.now()" "dt.replace(microsecond=0)" 500000 loops, best of 5: 501 nsec per loop $ python -m timeit -s "from datetime import date; d = date.today()" "d.replace(day=1)" 1000000 loops, best of 5: 273 nsec per loop $ python -m timeit -s "from datetime import time; t = time()" "t.replace(microsecond=0)" 1000000 loops, best of 5: 338 nsec per loop After: $ python -m timeit -s "from datetime import datetime; dt = datetime.now()" "dt.replace(microsecond=0)" 5000000 loops, best of 5: 65.9 nsec per loop $ python -m timeit -s "from datetime import date; d = date.today()" "d.replace(day=1)" 5000000 loops, best of 5: 45.6 nsec per loop $ python -m timeit -s "from datetime import time; t = time()" "t.replace(microsecond=0)" 5000000 loops, best of 5: 51 nsec per loop
1 parent 28b2b74 commit 36b9549

File tree

3 files changed

+429
-86
lines changed

3 files changed

+429
-86
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Speed-up :func:`datetime.datetime.replace`, :func:`datetime.date.replace` and
2+
:func:`datetime.time.replace`.
3+
.

Modules/_datetimemodule.c

+75-85
Original file line numberDiff line numberDiff line change
@@ -61,16 +61,6 @@ static datetime_state _datetime_global_state;
6161

6262
#define STATIC_STATE() (&_datetime_global_state)
6363

64-
/*[clinic input]
65-
module datetime
66-
class datetime.datetime "PyDateTime_DateTime *" "&PyDateTime_DateTimeType"
67-
class datetime.date "PyDateTime_Date *" "&PyDateTime_DateType"
68-
class datetime.IsoCalendarDate "PyDateTime_IsoCalendarDate *" "&PyDateTime_IsoCalendarDateType"
69-
[clinic start generated code]*/
70-
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=81bec0fa19837f63]*/
71-
72-
#include "clinic/_datetimemodule.c.h"
73-
7464
/* We require that C int be at least 32 bits, and use int virtually
7565
* everywhere. In just a few cases we use a temp long, where a Python
7666
* API returns a C long. In such cases, we have to ensure that the
@@ -161,6 +151,17 @@ static PyTypeObject PyDateTime_TimeZoneType;
161151

162152
static int check_tzinfo_subclass(PyObject *p);
163153

154+
/*[clinic input]
155+
module datetime
156+
class datetime.datetime "PyDateTime_DateTime *" "&PyDateTime_DateTimeType"
157+
class datetime.date "PyDateTime_Date *" "&PyDateTime_DateType"
158+
class datetime.time "PyDateTime_Time *" "&PyDateTime_TimeType"
159+
class datetime.IsoCalendarDate "PyDateTime_IsoCalendarDate *" "&PyDateTime_IsoCalendarDateType"
160+
[clinic start generated code]*/
161+
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=6f65a48dd22fa40f]*/
162+
163+
#include "clinic/_datetimemodule.c.h"
164+
164165

165166
/* ---------------------------------------------------------------------------
166167
* Math utilities.
@@ -3466,24 +3467,22 @@ date_timetuple(PyDateTime_Date *self, PyObject *Py_UNUSED(ignored))
34663467
0, 0, 0, -1);
34673468
}
34683469

3470+
/*[clinic input]
3471+
datetime.date.replace
3472+
3473+
year: int(c_default="GET_YEAR(self)") = unchanged
3474+
month: int(c_default="GET_MONTH(self)") = unchanged
3475+
day: int(c_default="GET_DAY(self)") = unchanged
3476+
3477+
Return date with new specified fields.
3478+
[clinic start generated code]*/
3479+
34693480
static PyObject *
3470-
date_replace(PyDateTime_Date *self, PyObject *args, PyObject *kw)
3481+
datetime_date_replace_impl(PyDateTime_Date *self, int year, int month,
3482+
int day)
3483+
/*[clinic end generated code: output=2a9430d1e6318aeb input=0d1f02685b3e90f6]*/
34713484
{
3472-
PyObject *clone;
3473-
PyObject *tuple;
3474-
int year = GET_YEAR(self);
3475-
int month = GET_MONTH(self);
3476-
int day = GET_DAY(self);
3477-
3478-
if (! PyArg_ParseTupleAndKeywords(args, kw, "|iii:replace", date_kws,
3479-
&year, &month, &day))
3480-
return NULL;
3481-
tuple = Py_BuildValue("iii", year, month, day);
3482-
if (tuple == NULL)
3483-
return NULL;
3484-
clone = date_new(Py_TYPE(self), tuple, NULL);
3485-
Py_DECREF(tuple);
3486-
return clone;
3485+
return new_date_ex(year, month, day, Py_TYPE(self));
34873486
}
34883487

34893488
static Py_hash_t
@@ -3596,10 +3595,9 @@ static PyMethodDef date_methods[] = {
35963595
PyDoc_STR("Return the day of the week represented by the date.\n"
35973596
"Monday == 0 ... Sunday == 6")},
35983597

3599-
{"replace", _PyCFunction_CAST(date_replace), METH_VARARGS | METH_KEYWORDS,
3600-
PyDoc_STR("Return date with new specified fields.")},
3598+
DATETIME_DATE_REPLACE_METHODDEF
36013599

3602-
{"__replace__", _PyCFunction_CAST(date_replace), METH_VARARGS | METH_KEYWORDS},
3600+
{"__replace__", _PyCFunction_CAST(datetime_date_replace), METH_FASTCALL | METH_KEYWORDS},
36033601

36043602
{"__reduce__", (PyCFunction)date_reduce, METH_NOARGS,
36053603
PyDoc_STR("__reduce__() -> (cls, state)")},
@@ -4573,36 +4571,33 @@ time_hash(PyDateTime_Time *self)
45734571
return self->hashcode;
45744572
}
45754573

4574+
/*[clinic input]
4575+
datetime.time.replace
4576+
4577+
hour: int(c_default="TIME_GET_HOUR(self)") = unchanged
4578+
minute: int(c_default="TIME_GET_MINUTE(self)") = unchanged
4579+
second: int(c_default="TIME_GET_SECOND(self)") = unchanged
4580+
microsecond: int(c_default="TIME_GET_MICROSECOND(self)") = unchanged
4581+
tzinfo: object(c_default="HASTZINFO(self) ? self->tzinfo : Py_None") = unchanged
4582+
*
4583+
fold: int(c_default="TIME_GET_FOLD(self)") = unchanged
4584+
4585+
Return time with new specified fields.
4586+
[clinic start generated code]*/
4587+
45764588
static PyObject *
4577-
time_replace(PyDateTime_Time *self, PyObject *args, PyObject *kw)
4589+
datetime_time_replace_impl(PyDateTime_Time *self, int hour, int minute,
4590+
int second, int microsecond, PyObject *tzinfo,
4591+
int fold)
4592+
/*[clinic end generated code: output=0b89a44c299e4f80 input=9b6a35b1e704b0ca]*/
45784593
{
4579-
PyObject *clone;
4580-
PyObject *tuple;
4581-
int hh = TIME_GET_HOUR(self);
4582-
int mm = TIME_GET_MINUTE(self);
4583-
int ss = TIME_GET_SECOND(self);
4584-
int us = TIME_GET_MICROSECOND(self);
4585-
PyObject *tzinfo = HASTZINFO(self) ? self->tzinfo : Py_None;
4586-
int fold = TIME_GET_FOLD(self);
4587-
4588-
if (! PyArg_ParseTupleAndKeywords(args, kw, "|iiiiO$i:replace",
4589-
time_kws,
4590-
&hh, &mm, &ss, &us, &tzinfo, &fold))
4591-
return NULL;
45924594
if (fold != 0 && fold != 1) {
45934595
PyErr_SetString(PyExc_ValueError,
45944596
"fold must be either 0 or 1");
45954597
return NULL;
45964598
}
4597-
tuple = Py_BuildValue("iiiiO", hh, mm, ss, us, tzinfo);
4598-
if (tuple == NULL)
4599-
return NULL;
4600-
clone = time_new(Py_TYPE(self), tuple, NULL);
4601-
if (clone != NULL) {
4602-
TIME_SET_FOLD(clone, fold);
4603-
}
4604-
Py_DECREF(tuple);
4605-
return clone;
4599+
return new_time_ex2(hour, minute, second, microsecond, tzinfo, fold,
4600+
Py_TYPE(self));
46064601
}
46074602

46084603
static PyObject *
@@ -4732,10 +4727,9 @@ static PyMethodDef time_methods[] = {
47324727
{"dst", (PyCFunction)time_dst, METH_NOARGS,
47334728
PyDoc_STR("Return self.tzinfo.dst(self).")},
47344729

4735-
{"replace", _PyCFunction_CAST(time_replace), METH_VARARGS | METH_KEYWORDS,
4736-
PyDoc_STR("Return time with new specified fields.")},
4730+
DATETIME_TIME_REPLACE_METHODDEF
47374731

4738-
{"__replace__", _PyCFunction_CAST(time_replace), METH_VARARGS | METH_KEYWORDS},
4732+
{"__replace__", _PyCFunction_CAST(datetime_time_replace), METH_FASTCALL | METH_KEYWORDS},
47394733

47404734
{"fromisoformat", (PyCFunction)time_fromisoformat, METH_O | METH_CLASS,
47414735
PyDoc_STR("string -> time from a string in ISO 8601 format")},
@@ -6042,40 +6036,37 @@ datetime_hash(PyDateTime_DateTime *self)
60426036
return self->hashcode;
60436037
}
60446038

6039+
/*[clinic input]
6040+
datetime.datetime.replace
6041+
6042+
year: int(c_default="GET_YEAR(self)") = unchanged
6043+
month: int(c_default="GET_MONTH(self)") = unchanged
6044+
day: int(c_default="GET_DAY(self)") = unchanged
6045+
hour: int(c_default="DATE_GET_HOUR(self)") = unchanged
6046+
minute: int(c_default="DATE_GET_MINUTE(self)") = unchanged
6047+
second: int(c_default="DATE_GET_SECOND(self)") = unchanged
6048+
microsecond: int(c_default="DATE_GET_MICROSECOND(self)") = unchanged
6049+
tzinfo: object(c_default="HASTZINFO(self) ? self->tzinfo : Py_None") = unchanged
6050+
*
6051+
fold: int(c_default="DATE_GET_FOLD(self)") = unchanged
6052+
6053+
Return datetime with new specified fields.
6054+
[clinic start generated code]*/
6055+
60456056
static PyObject *
6046-
datetime_replace(PyDateTime_DateTime *self, PyObject *args, PyObject *kw)
6057+
datetime_datetime_replace_impl(PyDateTime_DateTime *self, int year,
6058+
int month, int day, int hour, int minute,
6059+
int second, int microsecond, PyObject *tzinfo,
6060+
int fold)
6061+
/*[clinic end generated code: output=00bc96536833fddb input=9b38253d56d9bcad]*/
60476062
{
6048-
PyObject *clone;
6049-
PyObject *tuple;
6050-
int y = GET_YEAR(self);
6051-
int m = GET_MONTH(self);
6052-
int d = GET_DAY(self);
6053-
int hh = DATE_GET_HOUR(self);
6054-
int mm = DATE_GET_MINUTE(self);
6055-
int ss = DATE_GET_SECOND(self);
6056-
int us = DATE_GET_MICROSECOND(self);
6057-
PyObject *tzinfo = HASTZINFO(self) ? self->tzinfo : Py_None;
6058-
int fold = DATE_GET_FOLD(self);
6059-
6060-
if (! PyArg_ParseTupleAndKeywords(args, kw, "|iiiiiiiO$i:replace",
6061-
datetime_kws,
6062-
&y, &m, &d, &hh, &mm, &ss, &us,
6063-
&tzinfo, &fold))
6064-
return NULL;
60656063
if (fold != 0 && fold != 1) {
60666064
PyErr_SetString(PyExc_ValueError,
60676065
"fold must be either 0 or 1");
60686066
return NULL;
60696067
}
6070-
tuple = Py_BuildValue("iiiiiiiO", y, m, d, hh, mm, ss, us, tzinfo);
6071-
if (tuple == NULL)
6072-
return NULL;
6073-
clone = datetime_new(Py_TYPE(self), tuple, NULL);
6074-
if (clone != NULL) {
6075-
DATE_SET_FOLD(clone, fold);
6076-
}
6077-
Py_DECREF(tuple);
6078-
return clone;
6068+
return new_datetime_ex2(year, month, day, hour, minute, second,
6069+
microsecond, tzinfo, fold, Py_TYPE(self));
60796070
}
60806071

60816072
static PyObject *
@@ -6597,10 +6588,9 @@ static PyMethodDef datetime_methods[] = {
65976588
{"dst", (PyCFunction)datetime_dst, METH_NOARGS,
65986589
PyDoc_STR("Return self.tzinfo.dst(self).")},
65996590

6600-
{"replace", _PyCFunction_CAST(datetime_replace), METH_VARARGS | METH_KEYWORDS,
6601-
PyDoc_STR("Return datetime with new specified fields.")},
6591+
DATETIME_DATETIME_REPLACE_METHODDEF
66026592

6603-
{"__replace__", _PyCFunction_CAST(datetime_replace), METH_VARARGS | METH_KEYWORDS},
6593+
{"__replace__", _PyCFunction_CAST(datetime_datetime_replace), METH_FASTCALL | METH_KEYWORDS},
66046594

66056595
{"astimezone", _PyCFunction_CAST(datetime_astimezone), METH_VARARGS | METH_KEYWORDS,
66066596
PyDoc_STR("tz -> convert to local time in new timezone tz\n")},

0 commit comments

Comments
 (0)