// 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);
}
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);
}
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);
}
#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);
#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.
#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;
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 {
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);
}
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);
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);
}
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);
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
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);
}
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)
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);
args++;
nargsf--;
}
+ EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_METHOD, callable);
PyObject *result = _PyObject_VectorcallTstate(tstate, callable,
args, nargsf, kwnames);
Py_DECREF(callable);
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);
}
#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 *);
}
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;
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);
}
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) {
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;
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) ||
if (func == NULL) {
goto fail;
}
+ EVAL_CALL_STAT_INC(EVAL_CALL_LEGACY);
res = _PyEval_Vector(tstate, func, locals,
allargs, argcount,
kwnames);
)
{
PyObject *result;
-
if (PyCFunction_CheckExact(func) || PyCMethod_CheckExact(func)) {
C_TRACE(result, PyObject_Call(func, callargs, kwdict));
return result;
return result;
}
}
+ EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_FUNCTION_EX, func);
return PyObject_Call(func, callargs, kwdict);
}
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
MANAGED_DICT = 2,
OFFSET_DICT = 3,
NO_DICT = 4,
- LAZY_DICT = 5,
+ LAZY_DICT = 5,
} ObjectDictKind;
// Please collect stats carefully before and after modifying. A subtle change
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
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:])
))
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():
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}%"))