-
-
Notifications
You must be signed in to change notification settings - Fork 33.8k
gh-89013: Improve the performance of methodcaller (lazy version) #107201
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 11 commits
12a80bd
3d19683
035456c
8be0aed
83c76bb
09e3ee3
5d715c5
a6c465b
486c498
f174256
d70a8ad
1ceea51
608828a
ad7d6bd
3c3d73f
9335d6d
0c050fc
626169f
0634f66
5c56b64
812856b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Improve performance of ``operator.methodcaller`` by use of the the vectorcall protocol. Patch by Anthony Lee and Pieter Eendebak. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1549,16 +1549,90 @@ static PyType_Spec attrgetter_type_spec = { | |
| typedef struct { | ||
| PyObject_HEAD | ||
| PyObject *name; | ||
| PyObject *args; | ||
| PyObject *xargs; // reference to arguments passed in constructor | ||
| PyObject *kwds; | ||
| PyObject **vectorcall_args; /* Borrowed references */ | ||
| PyObject *vectorcall_kwnames; | ||
| vectorcallfunc vectorcall; | ||
| } methodcallerobject; | ||
|
|
||
| static void * _methodcaller_initialize_vectorcall(methodcallerobject* mc) | ||
eendebakpt marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| { | ||
| PyObject* args = mc->xargs; | ||
| PyObject* kwds = mc->kwds; | ||
|
|
||
| Py_ssize_t nargs = PyTuple_GET_SIZE(args); | ||
| mc->vectorcall_args = PyMem_Calloc( | ||
corona10 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| nargs + (kwds ? PyDict_Size(kwds) : 0), | ||
| sizeof(PyObject*)); | ||
| if (!mc->vectorcall_args) { | ||
| return PyErr_NoMemory(); | ||
| } | ||
| /* The first item of vectorcall_args will be filled with obj later */ | ||
| if (nargs > 1) { | ||
| memcpy(mc->vectorcall_args, PySequence_Fast_ITEMS(args), | ||
| nargs * sizeof(PyObject*)); | ||
| } | ||
| if (kwds) { | ||
| const Py_ssize_t nkwds = PyDict_Size(kwds); | ||
|
|
||
| mc->vectorcall_kwnames = PyTuple_New(nkwds); | ||
| if (!mc->vectorcall_kwnames) { | ||
| return NULL; | ||
| } | ||
| Py_ssize_t i = 0, ppos = 0; | ||
| PyObject* key, * value; | ||
| while (PyDict_Next(kwds, &ppos, &key, &value)) { | ||
| PyTuple_SET_ITEM(mc->vectorcall_kwnames, i, Py_NewRef(key)); | ||
| mc->vectorcall_args[nargs + i] = value; // borrowed reference | ||
| ++i; | ||
| } | ||
| } | ||
| else { | ||
| mc->vectorcall_kwnames = NULL; | ||
| } | ||
| return (void *)1; | ||
| } | ||
|
|
||
| static void _methodcaller_clear_vectorcall(methodcallerobject* mc) | ||
| { | ||
| if (mc->vectorcall_args != NULL) { | ||
| PyMem_Free(mc->vectorcall_args); | ||
| mc->vectorcall_args = 0; | ||
| Py_CLEAR(mc->vectorcall_kwnames); | ||
| } | ||
| } | ||
eendebakpt marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| static PyObject * | ||
| methodcaller_vectorcall( | ||
| methodcallerobject *mc, PyObject *const *args, size_t nargsf, PyObject* kwnames) | ||
| { | ||
| if (!_PyArg_CheckPositional("methodcaller", PyVectorcall_NARGS(nargsf), 1, 1) | ||
| || !_PyArg_NoKwnames("methodcaller", kwnames)) { | ||
| return NULL; | ||
| } | ||
| if (mc->vectorcall_args == NULL) { | ||
| void *ret = _methodcaller_initialize_vectorcall(mc); | ||
| if (ret == NULL) { | ||
| return NULL; | ||
| } | ||
eendebakpt marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| assert(mc->vectorcall_args != 0); | ||
| mc->vectorcall_args[0] = args[0]; | ||
| return PyObject_VectorcallMethod( | ||
| mc->name, mc->vectorcall_args, | ||
| (PyTuple_GET_SIZE(mc->xargs)) | PY_VECTORCALL_ARGUMENTS_OFFSET, | ||
| mc->vectorcall_kwnames); | ||
| } | ||
|
|
||
|
|
||
| /* AC 3.5: variable number of arguments, not currently support by AC */ | ||
| static PyObject * | ||
| methodcaller_new(PyTypeObject *type, PyObject *args, PyObject *kwds) | ||
| { | ||
| methodcallerobject *mc; | ||
| PyObject *name; | ||
| PyObject* name; | ||
eendebakpt marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| if (PyTuple_GET_SIZE(args) < 1) { | ||
| PyErr_SetString(PyExc_TypeError, "methodcaller needs at least " | ||
|
|
@@ -1580,30 +1654,28 @@ methodcaller_new(PyTypeObject *type, PyObject *args, PyObject *kwds) | |
| return NULL; | ||
| } | ||
|
|
||
| name = PyTuple_GET_ITEM(args, 0); | ||
| Py_INCREF(name); | ||
| PyUnicode_InternInPlace(&name); | ||
| mc->name = name; | ||
|
|
||
| mc->xargs = Py_XNewRef(args); // allows us to use borrowed references | ||
| mc->kwds = Py_XNewRef(kwds); | ||
| mc->vectorcall_args = 0; | ||
|
|
||
| mc->args = PyTuple_GetSlice(args, 1, PyTuple_GET_SIZE(args)); | ||
| if (mc->args == NULL) { | ||
| Py_DECREF(mc); | ||
| return NULL; | ||
| } | ||
|
|
||
| mc->vectorcall = (vectorcallfunc)methodcaller_vectorcall; | ||
|
|
||
| PyObject_GC_Track(mc); | ||
| return (PyObject *)mc; | ||
| } | ||
|
|
||
| static int | ||
| static void | ||
| methodcaller_clear(methodcallerobject *mc) | ||
| { | ||
| Py_CLEAR(mc->name); | ||
| Py_CLEAR(mc->args); | ||
| Py_CLEAR(mc->xargs); | ||
| Py_CLEAR(mc->kwds); | ||
| return 0; | ||
| _methodcaller_clear_vectorcall(mc); | ||
|
||
| } | ||
|
|
||
| static void | ||
|
|
@@ -1620,7 +1692,7 @@ static int | |
| methodcaller_traverse(methodcallerobject *mc, visitproc visit, void *arg) | ||
| { | ||
| Py_VISIT(mc->name); | ||
| Py_VISIT(mc->args); | ||
| Py_VISIT(mc->xargs); | ||
| Py_VISIT(mc->kwds); | ||
| Py_VISIT(Py_TYPE(mc)); | ||
| return 0; | ||
|
|
@@ -1639,7 +1711,16 @@ methodcaller_call(methodcallerobject *mc, PyObject *args, PyObject *kw) | |
| method = PyObject_GetAttr(obj, mc->name); | ||
| if (method == NULL) | ||
| return NULL; | ||
| result = PyObject_Call(method, mc->args, mc->kwds); | ||
|
|
||
|
|
||
| PyObject *cargs = PyTuple_GetSlice(mc->xargs, 1, PyTuple_GET_SIZE(mc->xargs)); | ||
| if (cargs == NULL) { | ||
| Py_DECREF(method); | ||
| return NULL; | ||
| } | ||
|
|
||
| result = PyObject_Call(method, cargs, mc->kwds); | ||
| Py_DECREF(cargs); | ||
| Py_DECREF(method); | ||
| return result; | ||
| } | ||
|
|
@@ -1657,7 +1738,7 @@ methodcaller_repr(methodcallerobject *mc) | |
| } | ||
|
|
||
| numkwdargs = mc->kwds != NULL ? PyDict_GET_SIZE(mc->kwds) : 0; | ||
| numposargs = PyTuple_GET_SIZE(mc->args); | ||
| numposargs = PyTuple_GET_SIZE(mc->xargs) - 1; | ||
| numtotalargs = numposargs + numkwdargs; | ||
|
|
||
| if (numtotalargs == 0) { | ||
|
|
@@ -1673,7 +1754,7 @@ methodcaller_repr(methodcallerobject *mc) | |
| } | ||
|
|
||
| for (i = 0; i < numposargs; ++i) { | ||
| PyObject *onerepr = PyObject_Repr(PyTuple_GET_ITEM(mc->args, i)); | ||
| PyObject *onerepr = PyObject_Repr(PyTuple_GET_ITEM(mc->xargs, i+1)); | ||
| if (onerepr == NULL) | ||
| goto done; | ||
| PyTuple_SET_ITEM(argreprs, i, onerepr); | ||
|
|
@@ -1723,17 +1804,16 @@ methodcaller_repr(methodcallerobject *mc) | |
| static PyObject * | ||
| methodcaller_reduce(methodcallerobject *mc, PyObject *Py_UNUSED(ignored)) | ||
| { | ||
| PyObject *newargs; | ||
| if (!mc->kwds || PyDict_GET_SIZE(mc->kwds) == 0) { | ||
| Py_ssize_t i; | ||
| Py_ssize_t callargcount = PyTuple_GET_SIZE(mc->args); | ||
| newargs = PyTuple_New(1 + callargcount); | ||
| Py_ssize_t newarg_size = PyTuple_GET_SIZE(mc->xargs); | ||
| PyObject * newargs = PyTuple_New(newarg_size); | ||
eendebakpt marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if (newargs == NULL) | ||
| return NULL; | ||
| PyTuple_SET_ITEM(newargs, 0, Py_NewRef(mc->name)); | ||
| for (i = 0; i < callargcount; ++i) { | ||
| PyObject *arg = PyTuple_GET_ITEM(mc->args, i); | ||
| PyTuple_SET_ITEM(newargs, i + 1, Py_NewRef(arg)); | ||
| for (i = 1; i < newarg_size; ++i) { | ||
| PyObject *arg = PyTuple_GET_ITEM(mc->xargs, i); | ||
| PyTuple_SET_ITEM(newargs, i, Py_NewRef(arg)); | ||
| } | ||
| return Py_BuildValue("ON", Py_TYPE(mc), newargs); | ||
| } | ||
|
|
@@ -1751,7 +1831,12 @@ methodcaller_reduce(methodcallerobject *mc, PyObject *Py_UNUSED(ignored)) | |
| constructor = PyObject_VectorcallDict(partial, newargs, 2, mc->kwds); | ||
|
|
||
| Py_DECREF(partial); | ||
| return Py_BuildValue("NO", constructor, mc->args); | ||
| PyObject *args = PyTuple_GetSlice(mc->xargs, 1, PyTuple_GET_SIZE(mc->xargs)); | ||
| if (!args) { | ||
| Py_DECREF(constructor); | ||
| return NULL; | ||
| } | ||
| return Py_BuildValue("NO", constructor, args); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -1760,6 +1845,12 @@ static PyMethodDef methodcaller_methods[] = { | |
| reduce_doc}, | ||
| {NULL} | ||
| }; | ||
|
|
||
| static PyMemberDef methodcaller_members[] = { | ||
| {"__vectorcalloffset__", T_PYSSIZET, offsetof(methodcallerobject, vectorcall), READONLY}, | ||
| {NULL} | ||
| }; | ||
|
|
||
| PyDoc_STRVAR(methodcaller_doc, | ||
| "methodcaller(name, /, *args, **kwargs)\n--\n\n\ | ||
| Return a callable object that calls the given method on its operand.\n\ | ||
|
|
@@ -1774,6 +1865,7 @@ static PyType_Slot methodcaller_type_slots[] = { | |
| {Py_tp_traverse, methodcaller_traverse}, | ||
| {Py_tp_clear, methodcaller_clear}, | ||
| {Py_tp_methods, methodcaller_methods}, | ||
| {Py_tp_members, methodcaller_members}, | ||
| {Py_tp_new, methodcaller_new}, | ||
| {Py_tp_getattro, PyObject_GenericGetAttr}, | ||
| {Py_tp_repr, methodcaller_repr}, | ||
|
|
@@ -1785,7 +1877,7 @@ static PyType_Spec methodcaller_type_spec = { | |
| .basicsize = sizeof(methodcallerobject), | ||
| .itemsize = 0, | ||
| .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | | ||
| Py_TPFLAGS_IMMUTABLETYPE), | ||
| Py_TPFLAGS_HAVE_VECTORCALL | Py_TPFLAGS_IMMUTABLETYPE), | ||
| .slots = methodcaller_type_slots, | ||
| }; | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.