Permalink
Browse files

Optimize slots: avoid temporary PyMethodObject

Issue #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 <songofacandy@gmail.com>
  • Loading branch information...
vstinner and methane committed Feb 9, 2017
1 parent c42c655 commit 516b98161a0e88fde85145ead571e13394215f8c
Showing with 166 additions and 58 deletions.
  1. +6 −0 Include/abstract.h
  2. +35 −0 Objects/abstract.c
  3. +125 −58 Objects/typeobject.c
View
@@ -257,6 +257,12 @@ PyAPI_FUNC(PyObject *) _PyObject_Call_Prepend(
PyObject *args,
PyObject *kwargs);
PyAPI_FUNC(PyObject *) _PyObject_FastCall_Prepend(
PyObject *callable,
PyObject *obj,
PyObject **args,
Py_ssize_t nargs);
PyAPI_FUNC(PyObject *) _Py_CheckFunctionResult(PyObject *callable,
PyObject *result,
const char *where);
View
@@ -2367,6 +2367,41 @@ _PyObject_FastCallDict(PyObject *callable, PyObject **args, Py_ssize_t nargs,
/* Positional arguments are obj followed by args:
call callable(obj, *args, **kwargs) */
PyObject *
_PyObject_FastCall_Prepend(PyObject *callable,
PyObject *obj, PyObject **args, Py_ssize_t nargs)
{
PyObject *small_stack[_PY_FASTCALL_SMALL_STACK];
PyObject **args2;
PyObject *result;
nargs++;
if (nargs <= (Py_ssize_t)Py_ARRAY_LENGTH(small_stack)) {
args2 = small_stack;
}
else {
args2 = PyMem_Malloc(nargs * sizeof(PyObject *));
if (args2 == NULL) {
PyErr_NoMemory();
return NULL;
}
}
/* use borrowed references */
args2[0] = obj;
memcpy(&args2[1],
args,
(nargs - 1)* sizeof(PyObject *));
result = _PyObject_FastCall(callable, args2, nargs);
if (args2 != small_stack) {
PyMem_Free(args2);
}
return result;
}
/* Call callable(obj, *args, **kwargs). */
PyObject *
_PyObject_Call_Prepend(PyObject *callable,
PyObject *obj, PyObject *args, PyObject *kwargs)
{
Oops, something went wrong.

0 comments on commit 516b981

Please sign in to comment.