mirror of https://github.com/python/cpython
GH-90230: Add stats to breakdown the origin of calls to `PyEval_EvalFrame` (GH-93284)
This commit is contained in:
parent
8995177030
commit
bbcf42449e
|
@ -103,6 +103,7 @@ _PyObject_CallNoArgsTstate(PyThreadState *tstate, PyObject *func) {
|
|||
// Private static inline function variant of public PyObject_CallNoArgs()
|
||||
static inline PyObject *
|
||||
_PyObject_CallNoArgs(PyObject *func) {
|
||||
EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_API, func);
|
||||
PyThreadState *tstate = _PyThreadState_GET();
|
||||
return _PyObject_VectorcallTstate(tstate, func, NULL, 0, NULL);
|
||||
}
|
||||
|
@ -111,6 +112,7 @@ _PyObject_CallNoArgs(PyObject *func) {
|
|||
static inline PyObject *
|
||||
_PyObject_FastCallTstate(PyThreadState *tstate, PyObject *func, PyObject *const *args, Py_ssize_t nargs)
|
||||
{
|
||||
EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_API, func);
|
||||
return _PyObject_VectorcallTstate(tstate, func, args, (size_t)nargs, NULL);
|
||||
}
|
||||
|
||||
|
|
|
@ -68,6 +68,7 @@ extern PyObject* _PyEval_BuiltinsFromGlobals(
|
|||
static inline PyObject*
|
||||
_PyEval_EvalFrame(PyThreadState *tstate, struct _PyInterpreterFrame *frame, int throwflag)
|
||||
{
|
||||
EVAL_CALL_STAT_INC(EVAL_CALL_TOTAL);
|
||||
if (tstate->interp->eval_frame == NULL) {
|
||||
return _PyEval_EvalFrameDefault(tstate, frame, throwflag);
|
||||
}
|
||||
|
|
|
@ -265,6 +265,9 @@ extern int _PyStaticCode_InternStrings(PyCodeObject *co);
|
|||
#define OBJECT_STAT_INC(name) _py_stats.object_stats.name++
|
||||
#define OBJECT_STAT_INC_COND(name, cond) \
|
||||
do { if (cond) _py_stats.object_stats.name++; } while (0)
|
||||
#define EVAL_CALL_STAT_INC(name) _py_stats.call_stats.eval_calls[name]++
|
||||
#define EVAL_CALL_STAT_INC_IF_FUNCTION(name, callable) \
|
||||
do { if (PyFunction_Check(callable)) _py_stats.call_stats.eval_calls[name]++; } while (0)
|
||||
|
||||
// Used by the _opcode extension which is built as a shared library
|
||||
PyAPI_FUNC(PyObject*) _Py_GetSpecializationStats(void);
|
||||
|
@ -276,6 +279,8 @@ PyAPI_FUNC(PyObject*) _Py_GetSpecializationStats(void);
|
|||
#define CALL_STAT_INC(name) ((void)0)
|
||||
#define OBJECT_STAT_INC(name) ((void)0)
|
||||
#define OBJECT_STAT_INC_COND(name, cond) ((void)0)
|
||||
#define EVAL_CALL_STAT_INC(name) ((void)0)
|
||||
#define EVAL_CALL_STAT_INC_IF_FUNCTION(name, callable) ((void)0)
|
||||
#endif // !Py_STATS
|
||||
|
||||
// Cache values are only valid in memory, so use native endianness.
|
||||
|
|
|
@ -10,6 +10,20 @@ extern "C" {
|
|||
|
||||
#define SPECIALIZATION_FAILURE_KINDS 32
|
||||
|
||||
/* Stats for determining who is calling PyEval_EvalFrame */
|
||||
#define EVAL_CALL_TOTAL 0
|
||||
#define EVAL_CALL_VECTOR 1
|
||||
#define EVAL_CALL_GENERATOR 2
|
||||
#define EVAL_CALL_LEGACY 3
|
||||
#define EVAL_CALL_FUNCTION_VECTORCALL 4
|
||||
#define EVAL_CALL_BUILD_CLASS 5
|
||||
#define EVAL_CALL_SLOT 6
|
||||
#define EVAL_CALL_FUNCTION_EX 7
|
||||
#define EVAL_CALL_API 8
|
||||
#define EVAL_CALL_METHOD 9
|
||||
|
||||
#define EVAL_CALL_KINDS 10
|
||||
|
||||
typedef struct _specialization_stats {
|
||||
uint64_t success;
|
||||
uint64_t failure;
|
||||
|
@ -31,6 +45,7 @@ typedef struct _call_stats {
|
|||
uint64_t pyeval_calls;
|
||||
uint64_t frames_pushed;
|
||||
uint64_t frame_objects_created;
|
||||
uint64_t eval_calls[EVAL_CALL_KINDS];
|
||||
} CallStats;
|
||||
|
||||
typedef struct _object_stats {
|
||||
|
|
|
@ -384,7 +384,7 @@ call_soon(PyObject *loop, PyObject *func, PyObject *arg, PyObject *ctx)
|
|||
nargs++;
|
||||
}
|
||||
stack[nargs] = (PyObject *)ctx;
|
||||
|
||||
EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_API, callable);
|
||||
handle = PyObject_Vectorcall(callable, stack, nargs, context_kwname);
|
||||
Py_DECREF(callable);
|
||||
}
|
||||
|
@ -2950,6 +2950,7 @@ task_step_impl(TaskObj *task, PyObject *exc)
|
|||
PyObject *stack[2];
|
||||
stack[0] = wrapper;
|
||||
stack[1] = (PyObject *)task->task_context;
|
||||
EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_API, add_cb);
|
||||
tmp = PyObject_Vectorcall(add_cb, stack, 1, context_kwname);
|
||||
Py_DECREF(add_cb);
|
||||
Py_DECREF(wrapper);
|
||||
|
|
|
@ -109,7 +109,9 @@ _Py_CheckSlotResult(PyObject *obj, const char *slot_name, int success)
|
|||
PyObject *
|
||||
PyObject_CallNoArgs(PyObject *func)
|
||||
{
|
||||
return _PyObject_CallNoArgs(func);
|
||||
EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_API, func);
|
||||
PyThreadState *tstate = _PyThreadState_GET();
|
||||
return _PyObject_VectorcallTstate(tstate, func, NULL, 0, NULL);
|
||||
}
|
||||
|
||||
|
||||
|
@ -322,7 +324,7 @@ _PyObject_Call(PyThreadState *tstate, PyObject *callable,
|
|||
assert(!_PyErr_Occurred(tstate));
|
||||
assert(PyTuple_Check(args));
|
||||
assert(kwargs == NULL || PyDict_Check(kwargs));
|
||||
|
||||
EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_API, callable);
|
||||
vectorcallfunc vector_func = _PyVectorcall_Function(callable);
|
||||
if (vector_func != NULL) {
|
||||
return _PyVectorcall_Call(tstate, vector_func, callable, args, kwargs);
|
||||
|
@ -367,6 +369,7 @@ PyCFunction_Call(PyObject *callable, PyObject *args, PyObject *kwargs)
|
|||
PyObject *
|
||||
PyObject_CallOneArg(PyObject *func, PyObject *arg)
|
||||
{
|
||||
EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_API, func);
|
||||
assert(arg != NULL);
|
||||
PyObject *_args[2];
|
||||
PyObject **args = _args + 1; // For PY_VECTORCALL_ARGUMENTS_OFFSET
|
||||
|
@ -389,6 +392,7 @@ _PyFunction_Vectorcall(PyObject *func, PyObject* const* stack,
|
|||
assert(nargs >= 0);
|
||||
PyThreadState *tstate = _PyThreadState_GET();
|
||||
assert(nargs == 0 || stack != NULL);
|
||||
EVAL_CALL_STAT_INC(EVAL_CALL_FUNCTION_VECTORCALL);
|
||||
if (((PyCodeObject *)f->func_code)->co_flags & CO_OPTIMIZED) {
|
||||
return _PyEval_Vector(tstate, f, NULL, stack, nargs, kwnames);
|
||||
}
|
||||
|
@ -520,7 +524,7 @@ _PyObject_CallFunctionVa(PyThreadState *tstate, PyObject *callable,
|
|||
if (stack == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_API, callable);
|
||||
if (nargs == 1 && PyTuple_Check(stack[0])) {
|
||||
/* Special cases for backward compatibility:
|
||||
- PyObject_CallFunction(func, "O", tuple) calls func(*tuple)
|
||||
|
@ -815,6 +819,11 @@ object_vacall(PyThreadState *tstate, PyObject *base,
|
|||
stack[i] = va_arg(vargs, PyObject *);
|
||||
}
|
||||
|
||||
#ifdef Py_STATS
|
||||
if (PyFunction_Check(callable)) {
|
||||
EVAL_CALL_STAT_INC(EVAL_CALL_API);
|
||||
}
|
||||
#endif
|
||||
/* Call the function */
|
||||
result = _PyObject_VectorcallTstate(tstate, callable, stack, nargs, NULL);
|
||||
|
||||
|
@ -852,6 +861,7 @@ PyObject_VectorcallMethod(PyObject *name, PyObject *const *args,
|
|||
args++;
|
||||
nargsf--;
|
||||
}
|
||||
EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_METHOD, callable);
|
||||
PyObject *result = _PyObject_VectorcallTstate(tstate, callable,
|
||||
args, nargsf, kwnames);
|
||||
Py_DECREF(callable);
|
||||
|
|
|
@ -1677,6 +1677,7 @@ property_descr_set(PyObject *self, PyObject *obj, PyObject *value)
|
|||
res = PyObject_CallOneArg(func, obj);
|
||||
}
|
||||
else {
|
||||
EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_API, func);
|
||||
PyObject *args[] = { obj, value };
|
||||
res = PyObject_Vectorcall(func, args, 2, NULL);
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include "pycore_pystate.h" // _PyThreadState_GET()
|
||||
#include "structmember.h" // PyMemberDef
|
||||
#include "opcode.h" // SEND
|
||||
#include "pystats.h"
|
||||
|
||||
static PyObject *gen_close(PyGenObject *, PyObject *);
|
||||
static PyObject *async_gen_asend_new(PyAsyncGenObject *, PyObject *);
|
||||
|
@ -218,6 +219,7 @@ gen_send_ex2(PyGenObject *gen, PyObject *arg, PyObject **presult,
|
|||
}
|
||||
|
||||
gen->gi_frame_state = FRAME_EXECUTING;
|
||||
EVAL_CALL_STAT_INC(EVAL_CALL_GENERATOR);
|
||||
result = _PyEval_EvalFrame(tstate, frame, exc);
|
||||
if (gen->gi_frame_state == FRAME_EXECUTING) {
|
||||
gen->gi_frame_state = FRAME_COMPLETED;
|
||||
|
|
|
@ -1653,6 +1653,7 @@ vectorcall_unbound(PyThreadState *tstate, int unbound, PyObject *func,
|
|||
args++;
|
||||
nargsf = nargsf - 1 + PY_VECTORCALL_ARGUMENTS_OFFSET;
|
||||
}
|
||||
EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_SLOT, func);
|
||||
return _PyObject_VectorcallTstate(tstate, func, args, nargsf, NULL);
|
||||
}
|
||||
|
||||
|
|
|
@ -198,6 +198,7 @@ builtin___build_class__(PyObject *self, PyObject *const *args, Py_ssize_t nargs,
|
|||
goto error;
|
||||
}
|
||||
PyThreadState *tstate = _PyThreadState_GET();
|
||||
EVAL_CALL_STAT_INC(EVAL_CALL_BUILD_CLASS);
|
||||
cell = _PyEval_Vector(tstate, (PyFunctionObject *)func, ns, NULL, 0, NULL);
|
||||
if (cell != NULL) {
|
||||
if (bases != orig_bases) {
|
||||
|
|
|
@ -1207,6 +1207,7 @@ PyEval_EvalCode(PyObject *co, PyObject *globals, PyObject *locals)
|
|||
if (func == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
EVAL_CALL_STAT_INC(EVAL_CALL_LEGACY);
|
||||
PyObject *res = _PyEval_Vector(tstate, func, locals, NULL, 0, NULL);
|
||||
Py_DECREF(func);
|
||||
return res;
|
||||
|
@ -6432,6 +6433,7 @@ _PyEval_Vector(PyThreadState *tstate, PyFunctionObject *func,
|
|||
if (frame == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
EVAL_CALL_STAT_INC(EVAL_CALL_VECTOR);
|
||||
PyObject *retval = _PyEval_EvalFrame(tstate, frame, 0);
|
||||
assert(
|
||||
_PyFrame_GetStackPointer(frame) == _PyFrame_Stackbase(frame) ||
|
||||
|
@ -6507,6 +6509,7 @@ PyEval_EvalCodeEx(PyObject *_co, PyObject *globals, PyObject *locals,
|
|||
if (func == NULL) {
|
||||
goto fail;
|
||||
}
|
||||
EVAL_CALL_STAT_INC(EVAL_CALL_LEGACY);
|
||||
res = _PyEval_Vector(tstate, func, locals,
|
||||
allargs, argcount,
|
||||
kwnames);
|
||||
|
@ -7299,7 +7302,6 @@ do_call_core(PyThreadState *tstate,
|
|||
)
|
||||
{
|
||||
PyObject *result;
|
||||
|
||||
if (PyCFunction_CheckExact(func) || PyCMethod_CheckExact(func)) {
|
||||
C_TRACE(result, PyObject_Call(func, callargs, kwdict));
|
||||
return result;
|
||||
|
@ -7329,6 +7331,7 @@ do_call_core(PyThreadState *tstate,
|
|||
return result;
|
||||
}
|
||||
}
|
||||
EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_FUNCTION_EX, func);
|
||||
return PyObject_Call(func, callargs, kwdict);
|
||||
}
|
||||
|
||||
|
|
|
@ -176,6 +176,9 @@ print_call_stats(FILE *out, CallStats *stats)
|
|||
fprintf(out, "Calls to Python functions inlined: %" PRIu64 "\n", stats->inlined_py_calls);
|
||||
fprintf(out, "Frames pushed: %" PRIu64 "\n", stats->frames_pushed);
|
||||
fprintf(out, "Frame objects created: %" PRIu64 "\n", stats->frame_objects_created);
|
||||
for (int i = 0; i < EVAL_CALL_KINDS; i++) {
|
||||
fprintf(out, "Calls via PyEval_EvalFrame[%d] : %" PRIu64 "\n", i, stats->eval_calls[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
|
@ -108,13 +108,14 @@ def extract_opcode_stats(stats):
|
|||
opcode_stats[int(n)][rest.strip(".")] = value
|
||||
return opcode_stats
|
||||
|
||||
def parse_kinds(spec_src):
|
||||
def parse_kinds(spec_src, prefix="SPEC_FAIL"):
|
||||
defines = collections.defaultdict(list)
|
||||
start = "#define " + prefix + "_"
|
||||
for line in spec_src:
|
||||
line = line.strip()
|
||||
if not line.startswith("#define SPEC_FAIL_"):
|
||||
if not line.startswith(start):
|
||||
continue
|
||||
line = line[len("#define SPEC_FAIL_"):]
|
||||
line = line[len(start):]
|
||||
name, val = line.split()
|
||||
defines[int(val.strip())].append(name.strip())
|
||||
return defines
|
||||
|
@ -129,8 +130,6 @@ def kind_to_text(kind, defines, opname):
|
|||
opname = "ATTR"
|
||||
if opname.endswith("SUBSCR"):
|
||||
opname = "SUBSCR"
|
||||
if opname.startswith("PRECALL"):
|
||||
opname = "CALL"
|
||||
for name in defines[kind]:
|
||||
if name.startswith(opname):
|
||||
return pretty(name[len(opname)+1:])
|
||||
|
@ -254,6 +253,9 @@ def emit_specialization_overview(opcode_stats, total):
|
|||
))
|
||||
|
||||
def emit_call_stats(stats):
|
||||
stats_path = os.path.join(os.path.dirname(__file__), "../../Include/pystats.h")
|
||||
with open(stats_path) as stats_src:
|
||||
defines = parse_kinds(stats_src, prefix="EVAL_CALL")
|
||||
with Section("Call stats", summary="Inlined calls and frame stats"):
|
||||
total = 0
|
||||
for key, value in stats.items():
|
||||
|
@ -263,6 +265,11 @@ def emit_call_stats(stats):
|
|||
for key, value in stats.items():
|
||||
if "Calls to" in key:
|
||||
rows.append((key, value, f"{100*value/total:0.1f}%"))
|
||||
elif key.startswith("Calls "):
|
||||
name, index = key[:-1].split("[")
|
||||
index = int(index)
|
||||
label = name + " (" + pretty(defines[index][0]) + ")"
|
||||
rows.append((label, value, f"{100*value/total:0.1f}%"))
|
||||
for key, value in stats.items():
|
||||
if key.startswith("Frame"):
|
||||
rows.append((key, value, f"{100*value/total:0.1f}%"))
|
||||
|
|
Loading…
Reference in New Issue