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
Lines changed: 2 additions & 0 deletions
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

Lines changed: 57 additions & 2 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"
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy