Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit f7c9144

Browse filesBrowse files
authored
gh-89013: Improve the performance of methodcaller (lazy version) (gh-107201)
1 parent 79e479c commit f7c9144
Copy full SHA for f7c9144

File tree

Expand file treeCollapse file tree

2 files changed

+114
-23
lines changed
Filter options
Expand file treeCollapse file tree

2 files changed

+114
-23
lines changed
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Improve performance of :func:`operator.methodcaller` using the :pep:`590` ``vectorcall`` convention.
2+
Patch by Anthony Lee and Pieter Eendebak.

‎Modules/_operator.c

Copy file name to clipboardExpand all lines: Modules/_operator.c
+112-23Lines changed: 112 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1549,10 +1549,77 @@ static PyType_Spec attrgetter_type_spec = {
15491549
typedef struct {
15501550
PyObject_HEAD
15511551
PyObject *name;
1552-
PyObject *args;
1552+
PyObject *xargs; // reference to arguments passed in constructor
15531553
PyObject *kwds;
1554+
PyObject **vectorcall_args; /* Borrowed references */
1555+
PyObject *vectorcall_kwnames;
1556+
vectorcallfunc vectorcall;
15541557
} methodcallerobject;
15551558

1559+
static int _methodcaller_initialize_vectorcall(methodcallerobject* mc)
1560+
{
1561+
PyObject* args = mc->xargs;
1562+
PyObject* kwds = mc->kwds;
1563+
1564+
Py_ssize_t nargs = PyTuple_GET_SIZE(args);
1565+
assert(nargs > 0);
1566+
mc->vectorcall_args = PyMem_Calloc(
1567+
nargs + (kwds ? PyDict_Size(kwds) : 0),
1568+
sizeof(PyObject*));
1569+
if (!mc->vectorcall_args) {
1570+
PyErr_NoMemory();
1571+
return -1;
1572+
}
1573+
/* The first item of vectorcall_args will be filled with obj later */
1574+
if (nargs > 1) {
1575+
memcpy(mc->vectorcall_args, PySequence_Fast_ITEMS(args),
1576+
nargs * sizeof(PyObject*));
1577+
}
1578+
if (kwds) {
1579+
const Py_ssize_t nkwds = PyDict_Size(kwds);
1580+
1581+
mc->vectorcall_kwnames = PyTuple_New(nkwds);
1582+
if (!mc->vectorcall_kwnames) {
1583+
return -1;
1584+
}
1585+
Py_ssize_t i = 0, ppos = 0;
1586+
PyObject* key, * value;
1587+
while (PyDict_Next(kwds, &ppos, &key, &value)) {
1588+
PyTuple_SET_ITEM(mc->vectorcall_kwnames, i, Py_NewRef(key));
1589+
mc->vectorcall_args[nargs + i] = value; // borrowed reference
1590+
++i;
1591+
}
1592+
}
1593+
else {
1594+
mc->vectorcall_kwnames = NULL;
1595+
}
1596+
return 1;
1597+
}
1598+
1599+
1600+
static PyObject *
1601+
methodcaller_vectorcall(
1602+
methodcallerobject *mc, PyObject *const *args, size_t nargsf, PyObject* kwnames)
1603+
{
1604+
if (!_PyArg_CheckPositional("methodcaller", PyVectorcall_NARGS(nargsf), 1, 1)
1605+
|| !_PyArg_NoKwnames("methodcaller", kwnames)) {
1606+
return NULL;
1607+
}
1608+
if (mc->vectorcall_args == NULL) {
1609+
if (_methodcaller_initialize_vectorcall(mc) < 0) {
1610+
return NULL;
1611+
}
1612+
}
1613+
1614+
assert(mc->vectorcall_args != 0);
1615+
mc->vectorcall_args[0] = args[0];
1616+
return PyObject_VectorcallMethod(
1617+
mc->name, mc->vectorcall_args,
1618+
(PyTuple_GET_SIZE(mc->xargs)) | PY_VECTORCALL_ARGUMENTS_OFFSET,
1619+
mc->vectorcall_kwnames);
1620+
}
1621+
1622+
15561623
/* AC 3.5: variable number of arguments, not currently support by AC */
15571624
static PyObject *
15581625
methodcaller_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
@@ -1580,38 +1647,40 @@ methodcaller_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
15801647
return NULL;
15811648
}
15821649

1583-
name = PyTuple_GET_ITEM(args, 0);
15841650
Py_INCREF(name);
15851651
PyUnicode_InternInPlace(&name);
15861652
mc->name = name;
15871653

1654+
mc->xargs = Py_XNewRef(args); // allows us to use borrowed references
15881655
mc->kwds = Py_XNewRef(kwds);
1656+
mc->vectorcall_args = 0;
15891657

1590-
mc->args = PyTuple_GetSlice(args, 1, PyTuple_GET_SIZE(args));
1591-
if (mc->args == NULL) {
1592-
Py_DECREF(mc);
1593-
return NULL;
1594-
}
1658+
1659+
mc->vectorcall = (vectorcallfunc)methodcaller_vectorcall;
15951660

15961661
PyObject_GC_Track(mc);
15971662
return (PyObject *)mc;
15981663
}
15991664

1600-
static int
1665+
static void
16011666
methodcaller_clear(methodcallerobject *mc)
16021667
{
16031668
Py_CLEAR(mc->name);
1604-
Py_CLEAR(mc->args);
1669+
Py_CLEAR(mc->xargs);
16051670
Py_CLEAR(mc->kwds);
1606-
return 0;
1671+
if (mc->vectorcall_args != NULL) {
1672+
PyMem_Free(mc->vectorcall_args);
1673+
mc->vectorcall_args = 0;
1674+
Py_CLEAR(mc->vectorcall_kwnames);
1675+
}
16071676
}
16081677

16091678
static void
16101679
methodcaller_dealloc(methodcallerobject *mc)
16111680
{
16121681
PyTypeObject *tp = Py_TYPE(mc);
16131682
PyObject_GC_UnTrack(mc);
1614-
(void)methodcaller_clear(mc);
1683+
methodcaller_clear(mc);
16151684
tp->tp_free(mc);
16161685
Py_DECREF(tp);
16171686
}
@@ -1620,7 +1689,7 @@ static int
16201689
methodcaller_traverse(methodcallerobject *mc, visitproc visit, void *arg)
16211690
{
16221691
Py_VISIT(mc->name);
1623-
Py_VISIT(mc->args);
1692+
Py_VISIT(mc->xargs);
16241693
Py_VISIT(mc->kwds);
16251694
Py_VISIT(Py_TYPE(mc));
16261695
return 0;
@@ -1639,7 +1708,16 @@ methodcaller_call(methodcallerobject *mc, PyObject *args, PyObject *kw)
16391708
method = PyObject_GetAttr(obj, mc->name);
16401709
if (method == NULL)
16411710
return NULL;
1642-
result = PyObject_Call(method, mc->args, mc->kwds);
1711+
1712+
1713+
PyObject *cargs = PyTuple_GetSlice(mc->xargs, 1, PyTuple_GET_SIZE(mc->xargs));
1714+
if (cargs == NULL) {
1715+
Py_DECREF(method);
1716+
return NULL;
1717+
}
1718+
1719+
result = PyObject_Call(method, cargs, mc->kwds);
1720+
Py_DECREF(cargs);
16431721
Py_DECREF(method);
16441722
return result;
16451723
}
@@ -1657,7 +1735,7 @@ methodcaller_repr(methodcallerobject *mc)
16571735
}
16581736

16591737
numkwdargs = mc->kwds != NULL ? PyDict_GET_SIZE(mc->kwds) : 0;
1660-
numposargs = PyTuple_GET_SIZE(mc->args);
1738+
numposargs = PyTuple_GET_SIZE(mc->xargs) - 1;
16611739
numtotalargs = numposargs + numkwdargs;
16621740

16631741
if (numtotalargs == 0) {
@@ -1673,7 +1751,7 @@ methodcaller_repr(methodcallerobject *mc)
16731751
}
16741752

16751753
for (i = 0; i < numposargs; ++i) {
1676-
PyObject *onerepr = PyObject_Repr(PyTuple_GET_ITEM(mc->args, i));
1754+
PyObject *onerepr = PyObject_Repr(PyTuple_GET_ITEM(mc->xargs, i+1));
16771755
if (onerepr == NULL)
16781756
goto done;
16791757
PyTuple_SET_ITEM(argreprs, i, onerepr);
@@ -1723,17 +1801,16 @@ methodcaller_repr(methodcallerobject *mc)
17231801
static PyObject *
17241802
methodcaller_reduce(methodcallerobject *mc, PyObject *Py_UNUSED(ignored))
17251803
{
1726-
PyObject *newargs;
17271804
if (!mc->kwds || PyDict_GET_SIZE(mc->kwds) == 0) {
17281805
Py_ssize_t i;
1729-
Py_ssize_t callargcount = PyTuple_GET_SIZE(mc->args);
1730-
newargs = PyTuple_New(1 + callargcount);
1806+
Py_ssize_t newarg_size = PyTuple_GET_SIZE(mc->xargs);
1807+
PyObject *newargs = PyTuple_New(newarg_size);
17311808
if (newargs == NULL)
17321809
return NULL;
17331810
PyTuple_SET_ITEM(newargs, 0, Py_NewRef(mc->name));
1734-
for (i = 0; i < callargcount; ++i) {
1735-
PyObject *arg = PyTuple_GET_ITEM(mc->args, i);
1736-
PyTuple_SET_ITEM(newargs, i + 1, Py_NewRef(arg));
1811+
for (i = 1; i < newarg_size; ++i) {
1812+
PyObject *arg = PyTuple_GET_ITEM(mc->xargs, i);
1813+
PyTuple_SET_ITEM(newargs, i, Py_NewRef(arg));
17371814
}
17381815
return Py_BuildValue("ON", Py_TYPE(mc), newargs);
17391816
}
@@ -1751,7 +1828,12 @@ methodcaller_reduce(methodcallerobject *mc, PyObject *Py_UNUSED(ignored))
17511828
constructor = PyObject_VectorcallDict(partial, newargs, 2, mc->kwds);
17521829

17531830
Py_DECREF(partial);
1754-
return Py_BuildValue("NO", constructor, mc->args);
1831+
PyObject *args = PyTuple_GetSlice(mc->xargs, 1, PyTuple_GET_SIZE(mc->xargs));
1832+
if (!args) {
1833+
Py_DECREF(constructor);
1834+
return NULL;
1835+
}
1836+
return Py_BuildValue("NO", constructor, args);
17551837
}
17561838
}
17571839

@@ -1760,6 +1842,12 @@ static PyMethodDef methodcaller_methods[] = {
17601842
reduce_doc},
17611843
{NULL}
17621844
};
1845+
1846+
static PyMemberDef methodcaller_members[] = {
1847+
{"__vectorcalloffset__", Py_T_PYSSIZET, offsetof(methodcallerobject, vectorcall), Py_READONLY},
1848+
{NULL}
1849+
};
1850+
17631851
PyDoc_STRVAR(methodcaller_doc,
17641852
"methodcaller(name, /, *args, **kwargs)\n--\n\n\
17651853
Return a callable object that calls the given method on its operand.\n\
@@ -1774,6 +1862,7 @@ static PyType_Slot methodcaller_type_slots[] = {
17741862
{Py_tp_traverse, methodcaller_traverse},
17751863
{Py_tp_clear, methodcaller_clear},
17761864
{Py_tp_methods, methodcaller_methods},
1865+
{Py_tp_members, methodcaller_members},
17771866
{Py_tp_new, methodcaller_new},
17781867
{Py_tp_getattro, PyObject_GenericGetAttr},
17791868
{Py_tp_repr, methodcaller_repr},
@@ -1785,7 +1874,7 @@ static PyType_Spec methodcaller_type_spec = {
17851874
.basicsize = sizeof(methodcallerobject),
17861875
.itemsize = 0,
17871876
.flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
1788-
Py_TPFLAGS_IMMUTABLETYPE),
1877+
Py_TPFLAGS_HAVE_VECTORCALL | Py_TPFLAGS_IMMUTABLETYPE),
17891878
.slots = methodcaller_type_slots,
17901879
};
17911880

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.