Skip to content

Commit 0a14506

Browse files
authored
bpo-44953: Add vectorcall for itemgetter and attrgetter instances (GH-27828)
1 parent d7a5aca commit 0a14506

File tree

2 files changed

+79
-12
lines changed

2 files changed

+79
-12
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Calling ``operator.itemgetter`` objects and ``operator.attrgetter`` objects is now faster due to use of the vectorcall calling convention.

Modules/_operator.c

Lines changed: 78 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "Python.h"
22
#include "pycore_moduleobject.h" // _PyModule_GetState()
3+
#include "structmember.h" // PyMemberDef
34
#include "pycore_runtime.h" // _Py_ID()
45
#include "clinic/_operator.c.h"
56

@@ -974,8 +975,15 @@ typedef struct {
974975
Py_ssize_t nitems;
975976
PyObject *item;
976977
Py_ssize_t index; // -1 unless *item* is a single non-negative integer index
978+
vectorcallfunc vectorcall;
977979
} itemgetterobject;
978980

981+
// Forward declarations
982+
static PyObject *
983+
itemgetter_vectorcall(PyObject *, PyObject *const *, size_t, PyObject *);
984+
static PyObject *
985+
itemgetter_call_impl(itemgetterobject *, PyObject *);
986+
979987
/* AC 3.5: treats first argument as an iterable, otherwise uses *args */
980988
static PyObject *
981989
itemgetter_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
@@ -1021,6 +1029,7 @@ itemgetter_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
10211029
}
10221030
}
10231031

1032+
ig->vectorcall = (vectorcallfunc)itemgetter_vectorcall;
10241033
PyObject_GC_Track(ig);
10251034
return (PyObject *)ig;
10261035
}
@@ -1053,16 +1062,33 @@ itemgetter_traverse(itemgetterobject *ig, visitproc visit, void *arg)
10531062
static PyObject *
10541063
itemgetter_call(itemgetterobject *ig, PyObject *args, PyObject *kw)
10551064
{
1056-
PyObject *obj, *result;
1057-
Py_ssize_t i, nitems=ig->nitems;
1058-
10591065
assert(PyTuple_CheckExact(args));
10601066
if (!_PyArg_NoKeywords("itemgetter", kw))
10611067
return NULL;
10621068
if (!_PyArg_CheckPositional("itemgetter", PyTuple_GET_SIZE(args), 1, 1))
10631069
return NULL;
1070+
return itemgetter_call_impl(ig, PyTuple_GET_ITEM(args, 0));
1071+
}
10641072

1065-
obj = PyTuple_GET_ITEM(args, 0);
1073+
static PyObject *
1074+
itemgetter_vectorcall(PyObject *ig, PyObject *const *args,
1075+
size_t nargsf, PyObject *kwnames)
1076+
{
1077+
if (!_PyArg_NoKwnames("itemgetter", kwnames)) {
1078+
return NULL;
1079+
}
1080+
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
1081+
if (!_PyArg_CheckPositional("itemgetter", nargs, 1, 1)) {
1082+
return NULL;
1083+
}
1084+
return itemgetter_call_impl((itemgetterobject *)ig, args[0]);
1085+
}
1086+
1087+
static PyObject *
1088+
itemgetter_call_impl(itemgetterobject *ig, PyObject *obj)
1089+
{
1090+
PyObject *result;
1091+
Py_ssize_t i, nitems=ig->nitems;
10661092
if (nitems == 1) {
10671093
if (ig->index >= 0
10681094
&& PyTuple_CheckExact(obj)
@@ -1130,6 +1156,11 @@ static PyMethodDef itemgetter_methods[] = {
11301156
{NULL}
11311157
};
11321158

1159+
static PyMemberDef itemgetter_members[] = {
1160+
{"__vectorcalloffset__", T_PYSSIZET, offsetof(itemgetterobject, vectorcall), READONLY},
1161+
{NULL} /* Sentinel */
1162+
};
1163+
11331164
PyDoc_STRVAR(itemgetter_doc,
11341165
"itemgetter(item, ...) --> itemgetter object\n\
11351166
\n\
@@ -1144,6 +1175,7 @@ static PyType_Slot itemgetter_type_slots[] = {
11441175
{Py_tp_traverse, itemgetter_traverse},
11451176
{Py_tp_clear, itemgetter_clear},
11461177
{Py_tp_methods, itemgetter_methods},
1178+
{Py_tp_members, itemgetter_members},
11471179
{Py_tp_new, itemgetter_new},
11481180
{Py_tp_getattro, PyObject_GenericGetAttr},
11491181
{Py_tp_repr, itemgetter_repr},
@@ -1155,7 +1187,7 @@ static PyType_Spec itemgetter_type_spec = {
11551187
.basicsize = sizeof(itemgetterobject),
11561188
.itemsize = 0,
11571189
.flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
1158-
Py_TPFLAGS_IMMUTABLETYPE),
1190+
Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_HAVE_VECTORCALL),
11591191
.slots = itemgetter_type_slots,
11601192
};
11611193

@@ -1165,8 +1197,15 @@ typedef struct {
11651197
PyObject_HEAD
11661198
Py_ssize_t nattrs;
11671199
PyObject *attr;
1200+
vectorcallfunc vectorcall;
11681201
} attrgetterobject;
11691202

1203+
// Forward declarations
1204+
static PyObject *
1205+
attrgetter_vectorcall(PyObject *, PyObject *const *, size_t, PyObject *);
1206+
static PyObject *
1207+
attrgetter_call_impl(attrgetterobject *, PyObject *);
1208+
11701209
/* AC 3.5: treats first argument as an iterable, otherwise uses *args */
11711210
static PyObject *
11721211
attrgetter_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
@@ -1210,7 +1249,7 @@ attrgetter_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
12101249
kind = PyUnicode_KIND(item);
12111250
data = PyUnicode_DATA(item);
12121251

1213-
/* check whethere the string is dotted */
1252+
/* check whether the string is dotted */
12141253
dot_count = 0;
12151254
for (char_idx = 0; char_idx < item_len; ++char_idx) {
12161255
if (PyUnicode_READ(kind, data, char_idx) == '.')
@@ -1276,6 +1315,7 @@ attrgetter_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
12761315

12771316
ag->attr = attr;
12781317
ag->nattrs = nattrs;
1318+
ag->vectorcall = (vectorcallfunc)attrgetter_vectorcall;
12791319

12801320
PyObject_GC_Track(ag);
12811321
return (PyObject *)ag;
@@ -1342,16 +1382,36 @@ dotted_getattr(PyObject *obj, PyObject *attr)
13421382
static PyObject *
13431383
attrgetter_call(attrgetterobject *ag, PyObject *args, PyObject *kw)
13441384
{
1345-
PyObject *obj, *result;
1346-
Py_ssize_t i, nattrs=ag->nattrs;
1347-
13481385
if (!_PyArg_NoKeywords("attrgetter", kw))
13491386
return NULL;
13501387
if (!_PyArg_CheckPositional("attrgetter", PyTuple_GET_SIZE(args), 1, 1))
13511388
return NULL;
1352-
obj = PyTuple_GET_ITEM(args, 0);
1353-
if (ag->nattrs == 1) /* ag->attr is always a tuple */
1389+
return attrgetter_call_impl(ag, PyTuple_GET_ITEM(args, 0));
1390+
}
1391+
1392+
static PyObject *
1393+
attrgetter_vectorcall(PyObject *ag, PyObject *const *args, size_t nargsf, PyObject *kwnames)
1394+
{
1395+
if (!_PyArg_NoKwnames("attrgetter", kwnames)) {
1396+
return NULL;
1397+
}
1398+
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
1399+
if (!_PyArg_CheckPositional("attrgetter", nargs, 1, 1)) {
1400+
return NULL;
1401+
}
1402+
return attrgetter_call_impl((attrgetterobject *)ag, args[0]);
1403+
}
1404+
1405+
static PyObject *
1406+
attrgetter_call_impl(attrgetterobject *ag, PyObject *obj)
1407+
{
1408+
PyObject *result;
1409+
Py_ssize_t i, nattrs=ag->nattrs;
1410+
1411+
if (ag->nattrs == 1) {
1412+
/* ag->attr is always a tuple */
13541413
return dotted_getattr(obj, PyTuple_GET_ITEM(ag->attr, 0));
1414+
}
13551415

13561416
assert(PyTuple_Check(ag->attr));
13571417
assert(PyTuple_GET_SIZE(ag->attr) == nattrs);
@@ -1460,6 +1520,11 @@ static PyMethodDef attrgetter_methods[] = {
14601520
{NULL}
14611521
};
14621522

1523+
static PyMemberDef attrgetter_members[] = {
1524+
{"__vectorcalloffset__", T_PYSSIZET, offsetof(attrgetterobject, vectorcall), READONLY},
1525+
{NULL} /* Sentinel*/
1526+
};
1527+
14631528
PyDoc_STRVAR(attrgetter_doc,
14641529
"attrgetter(attr, ...) --> attrgetter object\n\
14651530
\n\
@@ -1476,6 +1541,7 @@ static PyType_Slot attrgetter_type_slots[] = {
14761541
{Py_tp_traverse, attrgetter_traverse},
14771542
{Py_tp_clear, attrgetter_clear},
14781543
{Py_tp_methods, attrgetter_methods},
1544+
{Py_tp_members, attrgetter_members},
14791545
{Py_tp_new, attrgetter_new},
14801546
{Py_tp_getattro, PyObject_GenericGetAttr},
14811547
{Py_tp_repr, attrgetter_repr},
@@ -1487,7 +1553,7 @@ static PyType_Spec attrgetter_type_spec = {
14871553
.basicsize = sizeof(attrgetterobject),
14881554
.itemsize = 0,
14891555
.flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
1490-
Py_TPFLAGS_IMMUTABLETYPE),
1556+
Py_TPFLAGS_IMMUTABLETYPE | Py_TPFLAGS_HAVE_VECTORCALL),
14911557
.slots = attrgetter_type_slots,
14921558
};
14931559

0 commit comments

Comments
 (0)