Skip to content

Commit 8be0aed

Browse files
committed
optimize methodcaller construction
1 parent 035456c commit 8be0aed

File tree

2 files changed

+29
-48
lines changed

2 files changed

+29
-48
lines changed
Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
Calls to ``operator.methodcaller`` are now 25-33% faster thanks to the use of
2-
the vectorcall protocol.
1+
Improve performance of ``operator.methodcaller`` by use of the the vectorcall protocol. Patch by Anthony Lee and Pieter Eendebak.

Modules/_operator.c

Lines changed: 28 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include "pycore_modsupport.h" // _PyArg_NoKwnames()
33
#include "pycore_moduleobject.h" // _PyModule_GetState()
44
#include "pycore_runtime.h" // _Py_ID()
5+
56
#include "structmember.h" // PyMemberDef
67
#include "clinic/_operator.c.h"
78

@@ -1548,7 +1549,7 @@ static PyType_Spec attrgetter_type_spec = {
15481549
typedef struct {
15491550
PyObject_HEAD
15501551
PyObject *name;
1551-
PyObject *args;
1552+
PyObject *xargs; // reference to arguments passed in constructor
15521553
PyObject *kwds;
15531554
PyObject **vectorcall_args; /* Borrowed references */
15541555
PyObject *vectorcall_kwnames;
@@ -1566,7 +1567,7 @@ methodcaller_vectorcall(
15661567
mc->vectorcall_args[0] = args[0];
15671568
return PyObject_VectorcallMethod(
15681569
mc->name, mc->vectorcall_args,
1569-
(1 + PyTuple_GET_SIZE(mc->args)) | PY_VECTORCALL_ARGUMENTS_OFFSET,
1570+
(PyTuple_GET_SIZE(mc->xargs)) | PY_VECTORCALL_ARGUMENTS_OFFSET,
15701571
mc->vectorcall_kwnames);
15711572
}
15721573

@@ -1576,7 +1577,6 @@ methodcaller_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
15761577
{
15771578
methodcallerobject *mc;
15781579
PyObject *name, *key, *value;
1579-
Py_ssize_t nargs, i, ppos;
15801580

15811581
if (PyTuple_GET_SIZE(args) < 1) {
15821582
PyErr_SetString(PyExc_TypeError, "methodcaller needs at least "
@@ -1598,37 +1598,34 @@ methodcaller_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
15981598
return NULL;
15991599
}
16001600

1601-
name = PyTuple_GET_ITEM(args, 0);
16021601
Py_INCREF(name);
16031602
PyUnicode_InternInPlace(&name);
16041603
mc->name = name;
16051604

1605+
mc->xargs = Py_XNewRef(args); // allows us to use borrowed references
16061606
mc->kwds = Py_XNewRef(kwds);
16071607

1608-
mc->args = PyTuple_GetSlice(args, 1, PyTuple_GET_SIZE(args));
1609-
if (mc->args == NULL) {
1610-
Py_DECREF(mc);
1611-
return NULL;
1612-
}
1613-
1614-
nargs = PyTuple_GET_SIZE(args) - 1;
1608+
Py_ssize_t nargs = PyTuple_GET_SIZE(args);
16151609
mc->vectorcall_args = PyMem_Calloc(
1616-
1 + nargs + (kwds ? PyDict_Size(kwds) : 0),
1610+
nargs + (kwds ? PyDict_Size(kwds) : 0),
16171611
sizeof(PyObject *));
16181612
if (!mc->vectorcall_args) {
16191613
return PyErr_NoMemory();
16201614
}
1621-
/* The first item of vectorcall_args will be filled with obj. */
1622-
memcpy(mc->vectorcall_args + 1, PySequence_Fast_ITEMS(mc->args),
1615+
/* The first item of vectorcall_args will be filled with obj later */
1616+
if (nargs>1) {
1617+
memcpy(mc->vectorcall_args, PySequence_Fast_ITEMS(args),
16231618
nargs * sizeof(PyObject *));
1619+
}
16241620
if (kwds) {
16251621
mc->vectorcall_kwnames = PySequence_Tuple(kwds);
16261622
if (!mc->vectorcall_kwnames) {
16271623
return NULL;
16281624
}
1629-
i = ppos = 0;
1625+
Py_ssize_t i = 0;
1626+
Py_ssize_t ppos = 0;
16301627
while (PyDict_Next(kwds, &ppos, &key, &value)) {
1631-
mc->vectorcall_args[1 + nargs + i] = value;
1628+
mc->vectorcall_args[ nargs + i] = value;
16321629
++i;
16331630
}
16341631
}
@@ -1645,7 +1642,7 @@ static int
16451642
methodcaller_clear(methodcallerobject *mc)
16461643
{
16471644
Py_CLEAR(mc->name);
1648-
Py_CLEAR(mc->args);
1645+
Py_CLEAR(mc->xargs);
16491646
Py_CLEAR(mc->kwds);
16501647
Py_CLEAR(mc->vectorcall_kwnames);
16511648
return 0;
@@ -1666,30 +1663,12 @@ static int
16661663
methodcaller_traverse(methodcallerobject *mc, visitproc visit, void *arg)
16671664
{
16681665
Py_VISIT(mc->name);
1669-
Py_VISIT(mc->args);
1666+
Py_VISIT(mc->xargs);
16701667
Py_VISIT(mc->kwds);
16711668
Py_VISIT(Py_TYPE(mc));
16721669
return 0;
16731670
}
16741671

1675-
static PyObject *
1676-
methodcaller_call(methodcallerobject *mc, PyObject *args, PyObject *kw)
1677-
{
1678-
PyObject *method, *obj, *result;
1679-
1680-
if (!_PyArg_NoKeywords("methodcaller", kw))
1681-
return NULL;
1682-
if (!_PyArg_CheckPositional("methodcaller", PyTuple_GET_SIZE(args), 1, 1))
1683-
return NULL;
1684-
obj = PyTuple_GET_ITEM(args, 0);
1685-
method = PyObject_GetAttr(obj, mc->name);
1686-
if (method == NULL)
1687-
return NULL;
1688-
result = PyObject_Call(method, mc->args, mc->kwds);
1689-
Py_DECREF(method);
1690-
return result;
1691-
}
1692-
16931672
static PyObject *
16941673
methodcaller_repr(methodcallerobject *mc)
16951674
{
@@ -1703,7 +1682,7 @@ methodcaller_repr(methodcallerobject *mc)
17031682
}
17041683

17051684
numkwdargs = mc->kwds != NULL ? PyDict_GET_SIZE(mc->kwds) : 0;
1706-
numposargs = PyTuple_GET_SIZE(mc->args);
1685+
numposargs = PyTuple_GET_SIZE(mc->xargs) - 1;
17071686
numtotalargs = numposargs + numkwdargs;
17081687

17091688
if (numtotalargs == 0) {
@@ -1719,7 +1698,7 @@ methodcaller_repr(methodcallerobject *mc)
17191698
}
17201699

17211700
for (i = 0; i < numposargs; ++i) {
1722-
PyObject *onerepr = PyObject_Repr(PyTuple_GET_ITEM(mc->args, i));
1701+
PyObject *onerepr = PyObject_Repr(PyTuple_GET_ITEM(mc->xargs, i+1));
17231702
if (onerepr == NULL)
17241703
goto done;
17251704
PyTuple_SET_ITEM(argreprs, i, onerepr);
@@ -1769,17 +1748,16 @@ methodcaller_repr(methodcallerobject *mc)
17691748
static PyObject *
17701749
methodcaller_reduce(methodcallerobject *mc, PyObject *Py_UNUSED(ignored))
17711750
{
1772-
PyObject *newargs;
17731751
if (!mc->kwds || PyDict_GET_SIZE(mc->kwds) == 0) {
17741752
Py_ssize_t i;
1775-
Py_ssize_t callargcount = PyTuple_GET_SIZE(mc->args);
1776-
newargs = PyTuple_New(1 + callargcount);
1753+
Py_ssize_t newarg_size = PyTuple_GET_SIZE(mc->vectorcall_args);
1754+
PyObject * newargs = PyTuple_New(newarg_size);
17771755
if (newargs == NULL)
17781756
return NULL;
17791757
PyTuple_SET_ITEM(newargs, 0, Py_NewRef(mc->name));
1780-
for (i = 0; i < callargcount; ++i) {
1781-
PyObject *arg = PyTuple_GET_ITEM(mc->args, i);
1782-
PyTuple_SET_ITEM(newargs, i + 1, Py_NewRef(arg));
1758+
for (i = 1; i < newarg_size; ++i) {
1759+
PyObject *arg = PyTuple_GET_ITEM(mc->xargs, i);
1760+
PyTuple_SET_ITEM(newargs, i, Py_NewRef(arg));
17831761
}
17841762
return Py_BuildValue("ON", Py_TYPE(mc), newargs);
17851763
}
@@ -1797,7 +1775,12 @@ methodcaller_reduce(methodcallerobject *mc, PyObject *Py_UNUSED(ignored))
17971775
constructor = PyObject_VectorcallDict(partial, newargs, 2, mc->kwds);
17981776

17991777
Py_DECREF(partial);
1800-
return Py_BuildValue("NO", constructor, mc->args);
1778+
PyObject *args = PyTuple_GetSlice(mc->xargs, 1, PyTuple_GET_SIZE(mc->xargs));
1779+
if (!args) {
1780+
Py_DECREF(constructor);
1781+
return NULL;
1782+
}
1783+
return Py_BuildValue("NO", constructor, args);
18011784
}
18021785
}
18031786

@@ -1822,7 +1805,6 @@ r.name('date', foo=1).");
18221805
static PyType_Slot methodcaller_type_slots[] = {
18231806
{Py_tp_doc, (void *)methodcaller_doc},
18241807
{Py_tp_dealloc, methodcaller_dealloc},
1825-
{Py_tp_call, methodcaller_call},
18261808
{Py_tp_traverse, methodcaller_traverse},
18271809
{Py_tp_clear, methodcaller_clear},
18281810
{Py_tp_methods, methodcaller_methods},

0 commit comments

Comments
 (0)