8000 gh-89013: Improve the performance of methodcaller (lazy version) (gh-… · python/cpython@f7c9144 · GitHub
[go: up one dir, main page]

Skip to content

Commit f7c9144

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

File tree

2 files changed

+114
-23
lines changed

2 files changed

+114
-23
lines changed
Lines 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

Lines 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 67ED ->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< F438 code class="diff-text syntax-highlighted-line addition">+
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