Skip to content

Commit

Permalink
bpo-37017: PyObject_CallMethodObjArgs uses LOAD_METHOD optimization (G…
Browse files Browse the repository at this point in the history
…H-13516)

Update PyObject_CallMethodObjArgs and _PyObject_CallMethodIdObjArgs
to use _PyObject_GetMethod to avoid creating a bound method object
in many cases.

On a microbenchmark of PyObject_CallMethodObjArgs calling a method on
an interpreted Python class, this optimization resulted in a 1.7x
speedup.
  • Loading branch information
msullivan authored and methane committed May 26, 2019
1 parent 7114c65 commit 47dd2f9
Show file tree
Hide file tree
Showing 2 changed files with 33 additions and 16 deletions.
@@ -0,0 +1,4 @@
Update :c:func:`PyObject_CallMethodObjArgs` and ``_PyObject_CallMethodIdObjArgs``
to use ``_PyObject_GetMethod`` to avoid creating a bound method object in many
cases.
Patch by Michael J. Sullivan.
45 changes: 29 additions & 16 deletions Objects/call.c
Expand Up @@ -1159,7 +1159,7 @@ _PyObject_CallMethodId_SizeT(PyObject *obj, _Py_Identifier *name,
/* --- Call with "..." arguments ---------------------------------- */

static PyObject *
object_vacall(PyObject *callable, va_list vargs)
object_vacall(PyObject *base, PyObject *callable, va_list vargs)
{
PyObject *small_stack[_PY_FASTCALL_SMALL_STACK];
PyObject **stack;
Expand All @@ -1174,7 +1174,7 @@ object_vacall(PyObject *callable, va_list vargs)

/* Count the number of arguments */
va_copy(countva, vargs);
nargs = 0;
nargs = base ? 1 : 0;
while (1) {
PyObject *arg = va_arg(countva, PyObject *);
if (arg == NULL) {
Expand All @@ -1196,7 +1196,12 @@ object_vacall(PyObject *callable, va_list vargs)
}
}

for (i = 0; i < nargs; ++i) {
i = 0;
if (base) {
stack[i++] = base;
}

for (; i < nargs; ++i) {
stack[i] = va_arg(vargs, PyObject *);
}

Expand All @@ -1210,23 +1215,26 @@ object_vacall(PyObject *callable, va_list vargs)
}


/* Private API for the LOAD_METHOD opcode. */
extern int _PyObject_GetMethod(PyObject *, PyObject *, PyObject **);

PyObject *
PyObject_CallMethodObjArgs(PyObject *callable, PyObject *name, ...)
PyObject_CallMethodObjArgs(PyObject *obj, PyObject *name, ...)
{
va_list vargs;
PyObject *result;

if (callable == NULL || name == NULL) {
if (obj == NULL || name == NULL) {
return null_error();
}

callable = PyObject_GetAttr(callable, name);
PyObject *callable = NULL;
int is_method = _PyObject_GetMethod(obj, name, &callable);
if (callable == NULL) {
return NULL;
}
obj = is_method ? obj : NULL;

va_list vargs;
va_start(vargs, name);
result = object_vacall(callable, vargs);
PyObject *result = object_vacall(obj, callable, vargs);
va_end(vargs);

Py_DECREF(callable);
Expand All @@ -1238,20 +1246,25 @@ PyObject *
_PyObject_CallMethodIdObjArgs(PyObject *obj,
struct _Py_Identifier *name, ...)
{
va_list vargs;
PyObject *callable, *result;

if (obj == NULL || name == NULL) {
return null_error();
}

callable = _PyObject_GetAttrId(obj, name);
PyObject *oname = _PyUnicode_FromId(name); /* borrowed */
if (!oname) {
return NULL;
}

PyObject *callable = NULL;
int is_method = _PyObject_GetMethod(obj, oname, &callable);
if (callable == NULL) {
return NULL;
}
obj = is_method ? obj : NULL;

va_list vargs;
va_start(vargs, name);
result = object_vacall(callable, vargs);
PyObject *result = object_vacall(obj, callable, vargs);
va_end(vargs);

Py_DECREF(callable);
Expand All @@ -1266,7 +1279,7 @@ PyObject_CallFunctionObjArgs(PyObject *callable, ...)
PyObject *result;

va_start(vargs, callable);
result = object_vacall(callable, vargs);
result = object_vacall(NULL, callable, vargs);
va_end(vargs);

return result;
Expand Down

0 comments on commit 47dd2f9

Please sign in to comment.