From f8e7d88b94fc19e0e00aa4ce40fd9d4e4a3c43f5 Mon Sep 17 00:00:00 2001 From: sweeneyde Date: Thu, 19 Aug 2021 03:55:14 -0400 Subject: [PATCH 1/3] Add vectorcall for itemgetter objects --- Modules/_operator.c | 43 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/Modules/_operator.c b/Modules/_operator.c index f051513fc793a0..f020b0bfc41e53 100644 --- a/Modules/_operator.c +++ b/Modules/_operator.c @@ -1,5 +1,6 @@ #include "Python.h" #include "pycore_moduleobject.h" // _PyModule_GetState() +#include "structmember.h" // PyMemberDef #include "clinic/_operator.c.h" typedef struct { @@ -953,8 +954,15 @@ typedef struct { Py_ssize_t nitems; PyObject *item; Py_ssize_t index; // -1 unless *item* is a single non-negative integer index + vectorcallfunc vectorcall; } itemgetterobject; +// Forward declarations +static PyObject * +itemgetter_vectorcall(PyObject *, PyObject *const *, size_t, PyObject *); +static PyObject * +itemgetter_call_impl(itemgetterobject *, PyObject *); + /* AC 3.5: treats first argument as an iterable, otherwise uses *args */ static PyObject * itemgetter_new(PyTypeObject *type, PyObject *args, PyObject *kwds) @@ -1000,6 +1008,7 @@ itemgetter_new(PyTypeObject *type, PyObject *args, PyObject *kwds) } } + ig->vectorcall = (vectorcallfunc)itemgetter_vectorcall; PyObject_GC_Track(ig); return (PyObject *)ig; } @@ -1032,16 +1041,33 @@ itemgetter_traverse(itemgetterobject *ig, visitproc visit, void *arg) static PyObject * itemgetter_call(itemgetterobject *ig, PyObject *args, PyObject *kw) { - PyObject *obj, *result; - Py_ssize_t i, nitems=ig->nitems; - assert(PyTuple_CheckExact(args)); if (!_PyArg_NoKeywords("itemgetter", kw)) return NULL; if (!_PyArg_CheckPositional("itemgetter", PyTuple_GET_SIZE(args), 1, 1)) return NULL; + return itemgetter_call_impl(ig, PyTuple_GET_ITEM(args, 0)); +} - obj = PyTuple_GET_ITEM(args, 0); +static PyObject * +itemgetter_vectorcall(PyObject *ig, PyObject *const *args, + size_t nargsf, PyObject *kwnames) +{ + if (!_PyArg_NoKwnames("itemgetter", kwnames)) { + return NULL; + } + Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); + if (!_PyArg_CheckPositional("itemgetter", nargs, 1, 1)) { + return NULL; + } + return itemgetter_call_impl((itemgetterobject *)ig, args[0]); +} + +static PyObject * +itemgetter_call_impl(itemgetterobject *ig, PyObject *obj) +{ + PyObject *result; + Py_ssize_t i, nitems=ig->nitems; if (nitems == 1) { if (ig->index >= 0 && PyTuple_CheckExact(obj) @@ -1109,6 +1135,11 @@ static PyMethodDef itemgetter_methods[] = { {NULL} }; +static PyMemberDef itemgetter_members[] = { + {"__vectorcalloffset__", T_PYSSIZET, offsetof(itemgetterobject, vectorcall), READONLY}, + {NULL} /* Sentinel */ +}; + PyDoc_STRVAR(itemgetter_doc, "itemgetter(item, ...) --> itemgetter object\n\ \n\ @@ -1116,6 +1147,7 @@ Return a callable object that fetches the given item(s) from its operand.\n\ After f = itemgetter(2), the call f(r) returns r[2].\n\ After g = itemgetter(2, 5, 3), the call g(r) returns (r[2], r[5], r[3])"); + static PyType_Slot itemgetter_type_slots[] = { {Py_tp_doc, (void *)itemgetter_doc}, {Py_tp_dealloc, itemgetter_dealloc}, @@ -1123,6 +1155,7 @@ static PyType_Slot itemgetter_type_slots[] = { {Py_tp_traverse, itemgetter_traverse}, {Py_tp_clear, itemgetter_clear}, {Py_tp_methods, itemgetter_methods}, + {Py_tp_members, itemgetter_members}, {Py_tp_new, itemgetter_new}, {Py_tp_getattro, PyObject_GenericGetAttr}, {Py_tp_repr, itemgetter_repr}, @@ -1134,7 +1167,7 @@ static PyType_Spec itemgetter_type_spec = { .basicsize = sizeof(itemgetterobject), .itemsize = 0, .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | - Py_TPFLAGS_IMMUTABLETYPE), + Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_HAVE_VECTORCALL), .slots = itemgetter_type_slots, }; From 4033d273ce51a70ecaac95429fc0a6f2b1a89efd Mon Sep 17 00:00:00 2001 From: sweeneyde Date: Thu, 19 Aug 2021 04:40:19 -0400 Subject: [PATCH 2/3] Add vectorcall for attrgetter objects --- Modules/_operator.c | 49 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/Modules/_operator.c b/Modules/_operator.c index f020b0bfc41e53..7f1f81b9976cf3 100644 --- a/Modules/_operator.c +++ b/Modules/_operator.c @@ -1147,7 +1147,6 @@ Return a callable object that fetches the given item(s) from its operand.\n\ After f = itemgetter(2), the call f(r) returns r[2].\n\ After g = itemgetter(2, 5, 3), the call g(r) returns (r[2], r[5], r[3])"); - static PyType_Slot itemgetter_type_slots[] = { {Py_tp_doc, (void *)itemgetter_doc}, {Py_tp_dealloc, itemgetter_dealloc}, @@ -1177,8 +1176,15 @@ typedef struct { PyObject_HEAD Py_ssize_t nattrs; PyObject *attr; + vectorcallfunc vectorcall; } attrgetterobject; +// Forward declarations +static PyObject * +attrgetter_vectorcall(PyObject *, PyObject *const *, size_t, PyObject *); +static PyObject * +attrgetter_call_impl(attrgetterobject *, PyObject *); + /* AC 3.5: treats first argument as an iterable, otherwise uses *args */ static PyObject * attrgetter_new(PyTypeObject *type, PyObject *args, PyObject *kwds) @@ -1222,7 +1228,7 @@ attrgetter_new(PyTypeObject *type, PyObject *args, PyObject *kwds) kind = PyUnicode_KIND(item); data = PyUnicode_DATA(item); - /* check whethere the string is dotted */ + /* check whether the string is dotted */ dot_count = 0; for (char_idx = 0; char_idx < item_len; ++char_idx) { if (PyUnicode_READ(kind, data, char_idx) == '.') @@ -1288,6 +1294,7 @@ attrgetter_new(PyTypeObject *type, PyObject *args, PyObject *kwds) ag->attr = attr; ag->nattrs = nattrs; + ag->vectorcall = (vectorcallfunc)attrgetter_vectorcall; PyObject_GC_Track(ag); return (PyObject *)ag; @@ -1354,16 +1361,36 @@ dotted_getattr(PyObject *obj, PyObject *attr) static PyObject * attrgetter_call(attrgetterobject *ag, PyObject *args, PyObject *kw) { - PyObject *obj, *result; - Py_ssize_t i, nattrs=ag->nattrs; - if (!_PyArg_NoKeywords("attrgetter", kw)) return NULL; if (!_PyArg_CheckPositional("attrgetter", PyTuple_GET_SIZE(args), 1, 1)) return NULL; - obj = PyTuple_GET_ITEM(args, 0); - if (ag->nattrs == 1) /* ag->attr is always a tuple */ + return attrgetter_call_impl(ag, PyTuple_GET_ITEM(args, 0)); +} + +static PyObject * +attrgetter_vectorcall(PyObject *ag, PyObject *const *args, size_t nargsf, PyObject *kwnames) +{ + if (!_PyArg_NoKwnames("attrgetter", kwnames)) { + return NULL; + } + Py_ssize_t nargs = PyVectorcall_NARGS(nargsf); + if (!_PyArg_CheckPositional("attrgetter", nargs, 1, 1)) { + return NULL; + } + return attrgetter_call_impl((attrgetterobject *)ag, args[0]); +} + +static PyObject * +attrgetter_call_impl(attrgetterobject *ag, PyObject *obj) +{ + PyObject *result; + Py_ssize_t i, nattrs=ag->nattrs; + + if (ag->nattrs == 1) { + /* ag->attr is always a tuple */ return dotted_getattr(obj, PyTuple_GET_ITEM(ag->attr, 0)); + } assert(PyTuple_Check(ag->attr)); assert(PyTuple_GET_SIZE(ag->attr) == nattrs); @@ -1472,6 +1499,11 @@ static PyMethodDef attrgetter_methods[] = { {NULL} }; +static PyMemberDef attrgetter_members[] = { + {"__vectorcalloffset__", T_PYSSIZET, offsetof(attrgetterobject, vectorcall), READONLY}, + {NULL} /* Sentinel*/ +}; + PyDoc_STRVAR(attrgetter_doc, "attrgetter(attr, ...) --> attrgetter object\n\ \n\ @@ -1488,6 +1520,7 @@ static PyType_Slot attrgetter_type_slots[] = { {Py_tp_traverse, attrgetter_traverse}, {Py_tp_clear, attrgetter_clear}, {Py_tp_methods, attrgetter_methods}, + {Py_tp_members, attrgetter_members}, {Py_tp_new, attrgetter_new}, {Py_tp_getattro, PyObject_GenericGetAttr}, {Py_tp_repr, attrgetter_repr}, @@ -1499,7 +1532,7 @@ static PyType_Spec attrgetter_type_spec = { .basicsize = sizeof(attrgetterobject), .itemsize = 0, .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | - Py_TPFLAGS_IMMUTABLETYPE), + Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_HAVE_VECTORCALL), .slots = attrgetter_type_slots, }; From f9fc83a8fbc6e1f62a0bc022595a2063d613f250 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Thu, 19 Aug 2021 09:29:44 +0000 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NEWS.d/next/Library/2021-08-19-09-29-43.bpo-44953.27ZyUd.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2021-08-19-09-29-43.bpo-44953.27ZyUd.rst diff --git a/Misc/NEWS.d/next/Library/2021-08-19-09-29-43.bpo-44953.27ZyUd.rst b/Misc/NEWS.d/next/Library/2021-08-19-09-29-43.bpo-44953.27ZyUd.rst new file mode 100644 index 00000000000000..0d54ef006af3e5 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-08-19-09-29-43.bpo-44953.27ZyUd.rst @@ -0,0 +1 @@ +Calling ``operator.itemgetter`` objects and ``operator.attrgetter`` objects is now faster due to use of the vectorcall calling convention. \ No newline at end of file