Skip to content

Commit 12a80bd

Browse files
committed
bpo-44850: Speedup methodcaller via vectorcall.
1 parent 4f51fa9 commit 12a80bd

File tree

2 files changed

+59
-2
lines changed

2 files changed

+59
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Calls to ``operator.methodcaller`` are now 25-33% faster thanks to the use of
2+
the vectorcall protocol.

Modules/_operator.c

+57-2
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"
34
#include "clinic/_operator.c.h"
45

56
typedef struct {
@@ -1478,14 +1479,33 @@ typedef struct {
14781479
PyObject *name;
14791480
PyObject *args;
14801481
PyObject *kwds;
1482+
PyObject **vectorcall_args; /* Borrowed references */
1483+
PyObject *vectorcall_kwnames;
1484+
vectorcallfunc vectorcall;
14811485
} methodcallerobject;
14821486

1487+
static PyObject *
1488+
methodcaller_vectorcall(
1489+
methodcallerobject *mc, PyObject *const *args, size_t nargsf, PyObject* kwnames)
1490+
{
1491+
if (!_PyArg_CheckPositional("methodcaller", PyVectorcall_NARGS(nargsf), 1, 1)
1492+
|| !_PyArg_NoKwnames("methodcaller", kwnames)) {
1493+
return NULL;
1494+
}
1495+
mc->vectorcall_args[0] = args[0];
1496+
return PyObject_VectorcallMethod(
1497+
mc->name, mc->vectorcall_args,
1498+
(1 + PyTuple_GET_SIZE(mc->args)) | PY_VECTORCALL_ARGUMENTS_OFFSET,
1499+
mc->vectorcall_kwnames);
1500+
}
1501+
14831502
/* AC 3.5: variable number of arguments, not currently support by AC */
14841503
static PyObject *
14851504
methodcaller_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
14861505
{
14871506
methodcallerobject *mc;
1488-
PyObject *name;
1507+
PyObject *name, *key, *value;
1508+
Py_ssize_t nargs, i, ppos;
14891509

14901510
if (PyTuple_GET_SIZE(args) < 1) {
14911511
PyErr_SetString(PyExc_TypeError, "methodcaller needs at least "
@@ -1521,6 +1541,32 @@ methodcaller_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
15211541
return NULL;
15221542
}
15231543

1544+
nargs = PyTuple_GET_SIZE(args) - 1;
1545+
mc->vectorcall_args = PyMem_Calloc(
1546+
1 + nargs + (kwds ? PyDict_Size(kwds) : 0),
1547+
sizeof(PyObject *));
1548+
if (!mc->vectorcall_args) {
1549+
return PyErr_NoMemory();
1550+
}
1551+
/* The first item of vectorcall_args will be filled with obj. */
1552+
memcpy(mc->vectorcall_args + 1, PySequence_Fast_ITEMS(mc->args),
1553+
nargs * sizeof(PyObject *));
1554+
if (kwds) {
1555+
mc->vectorcall_kwnames = PySequence_Tuple(kwds);
1556+
if (!mc->vectorcall_kwnames) {
1557+
return NULL;
1558+
}
1559+
i = ppos = 0;
1560+
while (PyDict_Next(kwds, &ppos, &key, &value)) {
1561+
mc->vectorcall_args[1 + nargs + i] = value;
1562+
++i;
1563+
}
1564+
}
1565+
else {
1566+
mc->vectorcall_kwnames = NULL;
1567+
}
1568+
mc->vectorcall = (vectorcallfunc)methodcaller_vectorcall;
1569+
15241570
PyObject_GC_Track(mc);
15251571
return (PyObject *)mc;
15261572
}
@@ -1531,6 +1577,7 @@ methodcaller_clear(methodcallerobject *mc)
15311577
Py_CLEAR(mc->name);
15321578
Py_CLEAR(mc->args);
15331579
Py_CLEAR(mc->kwds);
1580+
Py_CLEAR(mc->vectorcall_kwnames);
15341581
return 0;
15351582
}
15361583

@@ -1540,6 +1587,7 @@ methodcaller_dealloc(methodcallerobject *mc)
15401587
PyTypeObject *tp = Py_TYPE(mc);
15411588
PyObject_GC_UnTrack(mc);
15421589
(void)methodcaller_clear(mc);
1590+
PyMem_Free(mc->vectorcall_args);
15431591
tp->tp_free(mc);
15441592
Py_DECREF(tp);
15451593
}
@@ -1696,6 +1744,12 @@ static PyMethodDef methodcaller_methods[] = {
16961744
reduce_doc},
16971745
{NULL}
16981746
};
1747+
1748+
static PyMemberDef methodcaller_members[] = {
1749+
{"__vectorcalloffset__", T_PYSSIZET, offsetof(methodcallerobject, vectorcall), READONLY},
1750+
{NULL}
1751+
};
1752+
16991753
PyDoc_STRVAR(methodcaller_doc,
17001754
"methodcaller(name, ...) --> methodcaller object\n\
17011755
\n\
@@ -1711,6 +1765,7 @@ static PyType_Slot methodcaller_type_slots[] = {
17111765
{Py_tp_traverse, methodcaller_traverse},
17121766
{Py_tp_clear, methodcaller_clear},
17131767
{Py_tp_methods, methodcaller_methods},
1768+
{Py_tp_members, methodcaller_members},
17141769
{Py_tp_new, methodcaller_new},
17151770
{Py_tp_getattro, PyObject_GenericGetAttr},
17161771
{Py_tp_repr, methodcaller_repr},
@@ -1722,7 +1777,7 @@ static PyType_Spec methodcaller_type_spec = {
17221777
.basicsize = sizeof(methodcallerobject),
17231778
.itemsize = 0,
17241779
.flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
1725-
Py_TPFLAGS_IMMUTABLETYPE),
1780+
Py_TPFLAGS_HAVE_VECTORCALL | Py_TPFLAGS_IMMUTABLETYPE),
17261781
.slots = methodcaller_type_slots,
17271782
};
17281783

0 commit comments

Comments
 (0)