Skip to content

Commit 8de28e4

Browse files
eendebakptclaude
andcommitted
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>
1 parent f051c68 commit 8de28e4

3 files changed

Lines changed: 72 additions & 50 deletions

File tree

Include/internal/pycore_call.h

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,6 @@ extern PyObject* _PyObject_Call(
5252
PyObject *args,
5353
PyObject *kwargs);
5454

55-
extern PyObject * _PyObject_CallMethodFormat(
56-
PyThreadState *tstate,
57-
PyObject *callable,
58-
const char *format,
59-
...);
60-
6155
// Export for 'array' shared extension
6256
PyAPI_FUNC(PyObject*) _PyObject_CallMethod(
6357
PyObject *obj,

Objects/call.c

Lines changed: 70 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -612,18 +612,66 @@ _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+
/* Resolve 'name' on 'obj' with _PyObject_GetMethod and call it directly,
616+
avoiding the bound-method object that PyObject_GetAttr()+call would allocate. */
617+
static PyObject *
618+
callmethod_va(PyObject *obj, PyObject *name,
619+
const char *format, va_list va)
617620
{
618-
assert(callable != NULL);
619-
if (!PyCallable_Check(callable)) {
621+
PyThreadState *tstate = _PyThreadState_GET();
622+
623+
PyObject *method = NULL;
624+
/* unbound: 1 -> 'method' is an unbound function, call method(obj, *args);
625+
0 -> 'method' is the resolved attribute, call method(*args). */
626+
int unbound = _PyObject_GetMethod(obj, name, &method);
627+
if (method == NULL) {
628+
return NULL;
629+
}
630+
if (!PyCallable_Check(method)) {
620631
_PyErr_Format(tstate, PyExc_TypeError,
621632
"attribute of type '%.200s' is not callable",
622-
Py_TYPE(callable)->tp_name);
633+
Py_TYPE(method)->tp_name);
634+
Py_DECREF(method);
623635
return NULL;
624636
}
625637

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

629677
PyObject *
@@ -635,17 +683,17 @@ PyObject_CallMethod(PyObject *obj, const char *name, const char *format, ...)
635683
return null_error(tstate);
636684
}
637685

638-
PyObject *callable = PyObject_GetAttrString(obj, name);
639-
if (callable == NULL) {
686+
PyObject *name_obj = PyUnicode_FromString(name);
687+
if (name_obj == NULL) {
640688
return NULL;
641689
}
642690

643691
va_list va;
644692
va_start(va, format);
645-
PyObject *retval = callmethod(tstate, callable, format, va);
693+
PyObject *retval = callmethod_va(obj, name_obj, format, va);
646694
va_end(va);
647695

648-
Py_DECREF(callable);
696+
Py_DECREF(name_obj);
649697
return retval;
650698
}
651699

@@ -660,17 +708,17 @@ PyEval_CallMethod(PyObject *obj, const char *name, const char *format, ...)
660708
return null_error(tstate);
661709
}
662710

663-
PyObject *callable = PyObject_GetAttrString(obj, name);
664-
if (callable == NULL) {
711+
PyObject *name_obj = PyUnicode_FromString(name);
712+
if (name_obj == NULL) {
665713
return NULL;
666714
}
667715

668716
va_list va;
669717
va_start(va, format);
670-
PyObject *retval = callmethod(tstate, callable, format, va);
718+
PyObject *retval = callmethod_va(obj, name_obj, format, va);
671719
va_end(va);
672720

673-
Py_DECREF(callable);
721+
Py_DECREF(name_obj);
674722
return retval;
675723
}
676724

@@ -684,17 +732,11 @@ _PyObject_CallMethod(PyObject *obj, PyObject *name,
684732
return null_error(tstate);
685733
}
686734

687-
PyObject *callable = PyObject_GetAttr(obj, name);
688-
if (callable == NULL) {
689-
return NULL;
690-
}
691-
692735
va_list va;
693736
va_start(va, format);
694-
PyObject *retval = callmethod(tstate, callable, format, va);
737+
PyObject *retval = callmethod_va(obj, name, format, va);
695738
va_end(va);
696739

697-
Py_DECREF(callable);
698740
return retval;
699741
}
700742

@@ -710,30 +752,17 @@ _PyObject_CallMethodId(PyObject *obj, _Py_Identifier *name,
710752

711753
_Py_COMP_DIAG_PUSH
712754
_Py_COMP_DIAG_IGNORE_DEPR_DECLS
713-
PyObject *callable = _PyObject_GetAttrId(obj, name);
755+
PyObject *name_obj = _PyUnicode_FromId(name); /* borrowed */
714756
_Py_COMP_DIAG_POP
715-
if (callable == NULL) {
757+
if (name_obj == NULL) {
716758
return NULL;
717759
}
718760

719761
va_list va;
720762
va_start(va, format);
721-
PyObject *retval = callmethod(tstate, callable, format, va);
763+
PyObject *retval = callmethod_va(obj, name_obj, format, va);
722764
va_end(va);
723765

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);
737766
return retval;
738767
}
739768

@@ -749,17 +778,17 @@ _PyObject_CallMethod_SizeT(PyObject *obj, const char *name,
749778
return null_error(tstate);
750779
}
751780

752-
PyObject *callable = PyObject_GetAttrString(obj, name);
753-
if (callable == NULL) {
781+
PyObject *name_obj = PyUnicode_FromString(name);
782+
if (name_obj == NULL) {
754783
return NULL;
755784
}
756785

757786
va_list va;
758787
va_start(va, format);
759-
PyObject *retval = callmethod(tstate, callable, format, va);
788+
PyObject *retval = callmethod_va(obj, name_obj, format, va);
760789
va_end(va);
761790

762-
Py_DECREF(callable);
791+
Py_DECREF(name_obj);
763792
return retval;
764793
}
765794

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
@@ -404,7 +404,6 @@ _Py_FindSourceFile(PyObject *filename, char* namebuf, size_t namelen, PyObject *
404404
tail++;
405405
taillen = strlen(tail);
406406

407-
PyThreadState *tstate = _PyThreadState_GET();
408407
if (PySys_GetOptionalAttr(&_Py_ID(path), &syspath) < 0) {
409408
PyErr_Clear();
410409
goto error;
@@ -444,7 +443,7 @@ _Py_FindSourceFile(PyObject *filename, char* namebuf, size_t namelen, PyObject *
444443
namebuf[len++] = SEP;
445444
strcpy(namebuf+len, tail);
446445

447-
binary = _PyObject_CallMethodFormat(tstate, open, "ss", namebuf, "rb");
446+
binary = PyObject_CallFunction(open, "ss", namebuf, "rb");
448447
if (binary != NULL) {
449448
result = binary;
450449
goto finally;

0 commit comments

Comments
 (0)