Skip to content

Commit 3f9118f

Browse files
eendebakptclaudekumaraditya303methane
authored
gh-151157: Speed up PyObject_CallMethod via _PyObject_GetMethod (#151158)
* Speed up PyObject_CallMethod via _PyObject_GetMethod PyObject_CallMethod (and _PyObject_CallMethod, PyEval_CallMethod, _PyObject_CallMethodId and the _SizeT variant) resolved the method with PyObject_GetAttr, which builds a temporary bound-method object on every call, then called it. Resolve the method with _PyObject_GetMethod instead (the same lookup the interpreter uses for obj.name(...)) and call it directly via _PyObject_VectorcallPrepend, skipping the bound-method allocation. Behaviour is unchanged: same attribute semantics, same "attribute of type ... is not callable" error, and the historical PyObject_CallMethod(o, m, "O", tuple) -> o.m(*tuple) unpacking. The shared helper callmethod() and _PyObject_CallMethodFormat() are no longer needed: their only caller (traceback.c) now uses PyObject_CallFunction, so both are removed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Use _PyObject_GetMethodStackRef so method calls scale in free-threading Resolve the method in callmethod_va() via _PyObject_GetMethodStackRef() instead of _PyObject_GetMethod(). The StackRef variant returns the method as a deferred reference, avoiding the per-call atomic refcount on the shared method object that otherwise serializes threads in the free-threaded build. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * rename to callmethod * review comments * review comments --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Kumar Aditya <kumaraditya@python.org> Co-authored-by: Inada Naoki <songofacandy@gmail.com>
1 parent cfd6cf7 commit 3f9118f

3 files changed

Lines changed: 75 additions & 49 deletions

File tree

‎Include/internal/pycore_call.h‎

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,6 @@ extern PyObject* _PyObject_Call(
5757
PyObject *args,
5858
PyObject *kwargs);
5959

60-
extern PyObject * _PyObject_CallMethodFormat(
61-
PyThreadState *tstate,
62-
PyObject *callable,
63-
const char *format,
64-
...);
65-
6660
// Export for 'array' shared extension
6761
PyAPI_FUNC(PyObject*) _PyObject_CallMethod(
6862
PyObject *obj,

‎Objects/call.c‎

Lines changed: 73 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -612,18 +612,70 @@ _PyObject_CallFunction_SizeT(PyObject *callable, const char *format, ...)
612612
}
613613

614614

615-
static PyObject*
616-
callmethod(PyThreadState *tstate, PyObject* callable, const char *format, va_list va)
615+
static PyObject *
616+
callmethod(PyThreadState *tstate, PyObject *obj, PyObject *name,
617+
const char *format, va_list va)
617618
{
618-
assert(callable != NULL);
619+
PyObject *result = NULL;
620+
621+
_PyCStackRef self, method;
622+
_PyThreadState_PushCStackRef(tstate, &self);
623+
_PyThreadState_PushCStackRef(tstate, &method);
624+
self.ref = PyStackRef_FromPyObjectBorrow(obj);
625+
626+
int res = _PyObject_GetMethodStackRef(tstate, &self.ref, name, &method.ref);
627+
if (res < 0) {
628+
goto exit;
629+
}
630+
631+
PyObject *callable = PyStackRef_AsPyObjectBorrow(method.ref);
632+
PyObject *self_obj = PyStackRef_AsPyObjectBorrow(self.ref);
633+
619634
if (!PyCallable_Check(callable)) {
620635
_PyErr_Format(tstate, PyExc_TypeError,
621636
"attribute of type '%.200s' is not callable",
622637
Py_TYPE(callable)->tp_name);
623-
return NULL;
638+
goto exit;
624639
}
625640

626-
return _PyObject_CallFunctionVa(tstate, callable, format, va);
641+
/* Build the positional arguments from the format string. */
642+
PyObject *small_stack[_PY_FASTCALL_SMALL_STACK];
643+
Py_ssize_t nargs = 0;
644+
PyObject **built = NULL;
645+
if (format != NULL && *format != '\0') {
646+
built = _Py_VaBuildStack(small_stack, _PY_FASTCALL_SMALL_STACK,
647+
format, va, &nargs);
648+
if (built == NULL) {
649+
goto exit;
650+
}
651+
}
652+
653+
/* Backward compat: a single tuple from "O" is unpacked. */
654+
PyObject *const *args = built;
655+
Py_ssize_t n = nargs;
656+
if (nargs == 1 && PyTuple_Check(built[0])) {
657+
args = _PyTuple_ITEMS(built[0]);
658+
n = PyTuple_GET_SIZE(built[0]);
659+
}
660+
661+
if (self_obj != NULL) {
662+
result = _PyObject_VectorcallPrepend(tstate, callable, self_obj, args, n, NULL);
663+
}
664+
else {
665+
result = _PyObject_VectorcallTstate(tstate, callable, args, n, NULL);
666+
}
667+
668+
for (Py_ssize_t i = 0; i < nargs; i++) {
669+
Py_DECREF(built[i]);
670+
}
671+
if (built != small_stack) {
672+
PyMem_Free(built);
673+
}
674+
675+
exit:
676+
_PyThreadState_PopCStackRef(tstate, &method);
677+
_PyThreadState_PopCStackRef(tstate, &self);
678+
return result;
627679
}
628680

629681
PyObject *
@@ -635,17 +687,17 @@ PyObject_CallMethod(PyObject *obj, const char *name, const char *format, ...)
635687
return null_error(tstate);
636688
}
637689

638-
PyObject *callable = PyObject_GetAttrString(obj, name);
639-
if (callable == NULL) {
690+
PyObject *name_obj = PyUnicode_FromString(name);
691+
if (name_obj == NULL) {
640692
return NULL;
641693
}
642694

643695
va_list va;
644696
va_start(va, format);
645-
PyObject *retval = callmethod(tstate, callable, format, va);
697+
PyObject *retval = callmethod(tstate, obj, name_obj, format, va);
646698
va_end(va);
647699

648-
Py_DECREF(callable);
700+
Py_DECREF(name_obj);
649701
return retval;
650702
}
651703

@@ -660,17 +712,17 @@ PyEval_CallMethod(PyObject *obj, const char *name, const char *format, ...)
660712
return null_error(tstate);
661713
}
662714

663-
PyObject *callable = PyObject_GetAttrString(obj, name);
664-
if (callable == NULL) {
715+
PyObject *name_obj = PyUnicode_FromString(name);
716+
if (name_obj == NULL) {
665717
return NULL;
666718
}
667719

668720
va_list va;
669721
va_start(va, format);
670-
PyObject *retval = callmethod(tstate, callable, format, va);
722+
PyObject *retval = callmethod(tstate, obj, name_obj, format, va);
671723
va_end(va);
672724

673-
Py_DECREF(callable);
725+
Py_DECREF(name_obj);
674726
return retval;
675727
}
676728

@@ -684,17 +736,11 @@ _PyObject_CallMethod(PyObject *obj, PyObject *name,
684736
return null_error(tstate);
685737
}
686738

687-
PyObject *callable = PyObject_GetAttr(obj, name);
688-
if (callable == NULL) {
689-
return NULL;
690-
}
691-
692739
va_list va;
693740
va_start(va, format);
694-
PyObject *retval = callmethod(tstate, callable, format, va);
741+
PyObject *retval = callmethod(tstate, obj, name, format, va);
695742
va_end(va);
696743

697-
Py_DECREF(callable);
698744
return retval;
699745
}
700746

@@ -710,30 +756,17 @@ _PyObject_CallMethodId(PyObject *obj, _Py_Identifier *name,
710756

711757
_Py_COMP_DIAG_PUSH
712758
_Py_COMP_DIAG_IGNORE_DEPR_DECLS
713-
PyObject *callable = _PyObject_GetAttrId(obj, name);
759+
PyObject *name_obj = _PyUnicode_FromId(name); /* borrowed */
714760
_Py_COMP_DIAG_POP
715-
if (callable == NULL) {
761+
if (name_obj == NULL) {
716762
return NULL;
717763
}
718764

719765
va_list va;
720766
va_start(va, format);
721-
PyObject *retval = callmethod(tstate, callable, format, va);
767+
PyObject *retval = callmethod(tstate, obj, name_obj, format, va);
722768
va_end(va);
723769

724-
Py_DECREF(callable);
725-
return retval;
726-
}
727-
728-
729-
PyObject * _PyObject_CallMethodFormat(PyThreadState *tstate, PyObject *callable,
730-
const char *format, ...)
731-
{
732-
assert(callable != NULL);
733-
va_list va;
734-
va_start(va, format);
735-
PyObject *retval = callmethod(tstate, callable, format, va);
736-
va_end(va);
737770
return retval;
738771
}
739772

@@ -749,17 +782,17 @@ _PyObject_CallMethod_SizeT(PyObject *obj, const char *name,
749782
return null_error(tstate);
750783
}
751784

752-
PyObject *callable = PyObject_GetAttrString(obj, name);
753-
if (callable == NULL) {
785+
PyObject *name_obj = PyUnicode_FromString(name);
786+
if (name_obj == NULL) {
754787
return NULL;
755788
}
756789

757790
va_list va;
758791
va_start(va, format);
759-
PyObject *retval = callmethod(tstate, callable, format, va);
792+
PyObject *retval = callmethod(tstate, obj, name_obj, format, va);
760793
va_end(va);
761794

762-
Py_DECREF(callable);
795+
Py_DECREF(name_obj);
763796
return retval;
764797
}
765798

‎Python/traceback.c‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
/* Traceback implementation */
33

44
#include "Python.h"
5-
#include "pycore_call.h" // _PyObject_CallMethodFormat()
5+
#include "pycore_call.h" // _PyObject_CallMethod()
66
#include "pycore_fileutils.h" // _Py_BEGIN_SUPPRESS_IPH
77
#include "pycore_frame.h" // PyFrameObject
88
#include "pycore_interp.h" // PyInterpreterState.gc
@@ -397,7 +397,6 @@ _Py_FindSourceFile(PyObject *filename, char* namebuf, size_t namelen, PyObject *
397397
tail++;
398398
taillen = strlen(tail);
399399

400-
PyThreadState *tstate = _PyThreadState_GET();
401400
if (PySys_GetOptionalAttr(&_Py_ID(path), &syspath) < 0) {
402401
PyErr_Clear();
403402
goto error;
@@ -437,7 +436,7 @@ _Py_FindSourceFile(PyObject *filename, char* namebuf, size_t namelen, PyObject *
437436
namebuf[len++] = SEP;
438437
strcpy(namebuf+len, tail);
439438

440-
binary = _PyObject_CallMethodFormat(tstate, open, "ss", namebuf, "rb");
439+
binary = PyObject_CallFunction(open, "ss", namebuf, "rb");
441440
if (binary != NULL) {
442441
result = binary;
443442
goto finally;

0 commit comments

Comments
 (0)