Skip to content

Commit 516b981

Browse files
vstinnermethane
andcommitted
Optimize slots: avoid temporary PyMethodObject
Issue python#29507: Optimize slots calling Python methods. For Python methods, get the unbound Python function and prepend arguments with self, rather than calling the descriptor which creates a temporary PyMethodObject. Add a new _PyObject_FastCall_Prepend() function used to call the unbound Python method with self. It avoids the creation of a temporary tuple to pass positional arguments. Avoiding temporary PyMethodObject and avoiding temporary tuple makes Python slots up to 1.46x faster. Microbenchmark on a __getitem__() method implemented in Python: Median +- std dev: 121 ns +- 5 ns -> 82.8 ns +- 1.0 ns: 1.46x faster (-31%) Co-Authored-by: INADA Naoki <[email protected]>
1 parent c42c655 commit 516b981

File tree

3 files changed

+166
-58
lines changed

3 files changed

+166
-58
lines changed

Include/abstract.h

+6
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,12 @@ PyAPI_FUNC(PyObject *) _PyObject_Call_Prepend(
257257
PyObject *args,
258258
PyObject *kwargs);
259259

260+
PyAPI_FUNC(PyObject *) _PyObject_FastCall_Prepend(
261+
PyObject *callable,
262+
PyObject *obj,
263+
PyObject **args,
264+
Py_ssize_t nargs);
265+
260266
PyAPI_FUNC(PyObject *) _Py_CheckFunctionResult(PyObject *callable,
261267
PyObject *result,
262268
const char *where);

Objects/abstract.c

+35
Original file line numberDiff line numberDiff line change
@@ -2367,6 +2367,41 @@ _PyObject_FastCallDict(PyObject *callable, PyObject **args, Py_ssize_t nargs,
23672367
/* Positional arguments are obj followed by args:
23682368
call callable(obj, *args, **kwargs) */
23692369
PyObject *
2370+
_PyObject_FastCall_Prepend(PyObject *callable,
2371+
PyObject *obj, PyObject **args, Py_ssize_t nargs)
2372+
{
2373+
PyObject *small_stack[_PY_FASTCALL_SMALL_STACK];
2374+
PyObject **args2;
2375+
PyObject *result;
2376+
2377+
nargs++;
2378+
if (nargs <= (Py_ssize_t)Py_ARRAY_LENGTH(small_stack)) {
2379+
args2 = small_stack;
2380+
}
2381+
else {
2382+
args2 = PyMem_Malloc(nargs * sizeof(PyObject *));
2383+
if (args2 == NULL) {
2384+
PyErr_NoMemory();
2385+
return NULL;
2386+
}
2387+
}
2388+
2389+
/* use borrowed references */
2390+
args2[0] = obj;
2391+
memcpy(&args2[1],
2392+
args,
2393+
(nargs - 1)* sizeof(PyObject *));
2394+
2395+
result = _PyObject_FastCall(callable, args2, nargs);
2396+
if (args2 != small_stack) {
2397+
PyMem_Free(args2);
2398+
}
2399+
return result;
2400+
}
2401+
2402+
2403+
/* Call callable(obj, *args, **kwargs). */
2404+
PyObject *
23702405
_PyObject_Call_Prepend(PyObject *callable,
23712406
PyObject *obj, PyObject *args, PyObject *kwargs)
23722407
{

0 commit comments

Comments
 (0)