Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit bbcf424

Browse filesBrowse files
authored
GH-90230: Add stats to breakdown the origin of calls to PyEval_EvalFrame (GH-93284)
1 parent 8995177 commit bbcf424
Copy full SHA for bbcf424

File tree

Expand file treeCollapse file tree

13 files changed

+63
-11
lines changed
Filter options
Expand file treeCollapse file tree

13 files changed

+63
-11
lines changed

‎Include/internal/pycore_call.h

Copy file name to clipboardExpand all lines: Include/internal/pycore_call.h
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ _PyObject_CallNoArgsTstate(PyThreadState *tstate, PyObject *func) {
103103
// Private static inline function variant of public PyObject_CallNoArgs()
104104
static inline PyObject *
105105
_PyObject_CallNoArgs(PyObject *func) {
106+
EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_API, func);
106107
PyThreadState *tstate = _PyThreadState_GET();
107108
return _PyObject_VectorcallTstate(tstate, func, NULL, 0, NULL);
108109
}
@@ -111,6 +112,7 @@ _PyObject_CallNoArgs(PyObject *func) {
111112
static inline PyObject *
112113
_PyObject_FastCallTstate(PyThreadState *tstate, PyObject *func, PyObject *const *args, Py_ssize_t nargs)
113114
{
115+
EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_API, func);
114116
return _PyObject_VectorcallTstate(tstate, func, args, (size_t)nargs, NULL);
115117
}
116118

‎Include/internal/pycore_ceval.h

Copy file name to clipboardExpand all lines: Include/internal/pycore_ceval.h
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ extern PyObject* _PyEval_BuiltinsFromGlobals(
6868
static inline PyObject*
6969
_PyEval_EvalFrame(PyThreadState *tstate, struct _PyInterpreterFrame *frame, int throwflag)
7070
{
71+
EVAL_CALL_STAT_INC(EVAL_CALL_TOTAL);
7172
if (tstate->interp->eval_frame == NULL) {
7273
return _PyEval_EvalFrameDefault(tstate, frame, throwflag);
7374
}

‎Include/internal/pycore_code.h

Copy file name to clipboardExpand all lines: Include/internal/pycore_code.h
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,9 @@ extern int _PyStaticCode_InternStrings(PyCodeObject *co);
265265
#define OBJECT_STAT_INC(name) _py_stats.object_stats.name++
266266
#define OBJECT_STAT_INC_COND(name, cond) \
267267
do { if (cond) _py_stats.object_stats.name++; } while (0)
268+
#define EVAL_CALL_STAT_INC(name) _py_stats.call_stats.eval_calls[name]++
269+
#define EVAL_CALL_STAT_INC_IF_FUNCTION(name, callable) \
270+
do { if (PyFunction_Check(callable)) _py_stats.call_stats.eval_calls[name]++; } while (0)
268271

269272
// Used by the _opcode extension which is built as a shared library
270273
PyAPI_FUNC(PyObject*) _Py_GetSpecializationStats(void);
@@ -276,6 +279,8 @@ PyAPI_FUNC(PyObject*) _Py_GetSpecializationStats(void);
276279
#define CALL_STAT_INC(name) ((void)0)
277280
#define OBJECT_STAT_INC(name) ((void)0)
278281
#define OBJECT_STAT_INC_COND(name, cond) ((void)0)
282+
#define EVAL_CALL_STAT_INC(name) ((void)0)
283+
#define EVAL_CALL_STAT_INC_IF_FUNCTION(name, callable) ((void)0)
279284
#endif // !Py_STATS
280285

281286
// Cache values are only valid in memory, so use native endianness.

‎Include/pystats.h

Copy file name to clipboardExpand all lines: Include/pystats.h
+15Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,20 @@ extern "C" {
1010

1111
#define SPECIALIZATION_FAILURE_KINDS 32
1212

13+
/* Stats for determining who is calling PyEval_EvalFrame */
14+
#define EVAL_CALL_TOTAL 0
15+
#define EVAL_CALL_VECTOR 1
16+
#define EVAL_CALL_GENERATOR 2
17+
#define EVAL_CALL_LEGACY 3
18+
#define EVAL_CALL_FUNCTION_VECTORCALL 4
19+
#define EVAL_CALL_BUILD_CLASS 5
20+
#define EVAL_CALL_SLOT 6
21+
#define EVAL_CALL_FUNCTION_EX 7
22+
#define EVAL_CALL_API 8
23+
#define EVAL_CALL_METHOD 9
24+
25+
#define EVAL_CALL_KINDS 10
26+
1327
typedef struct _specialization_stats {
1428
uint64_t success;
1529
uint64_t failure;
@@ -31,6 +45,7 @@ typedef struct _call_stats {
3145
uint64_t pyeval_calls;
3246
uint64_t frames_pushed;
3347
uint64_t frame_objects_created;
48+
uint64_t eval_calls[EVAL_CALL_KINDS];
3449
} CallStats;
3550

3651
typedef struct _object_stats {

‎Modules/_asynciomodule.c

Copy file name to clipboardExpand all lines: Modules/_asynciomodule.c
+2-1Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ call_soon(PyObject *loop, PyObject *func, PyObject *arg, PyObject *ctx)
384384
nargs++;
385385
}
386386
stack[nargs] = (PyObject *)ctx;
387-
387+
EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_API, callable);
388388
handle = PyObject_Vectorcall(callable, stack, nargs, context_kwname);
389389
Py_DECREF(callable);
390390
}
@@ -2950,6 +2950,7 @@ task_step_impl(TaskObj *task, PyObject *exc)
29502950
PyObject *stack[2];
29512951
stack[0] = wrapper;
29522952
stack[1] = (PyObject *)task->task_context;
2953+
EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_API, add_cb);
29532954
tmp = PyObject_Vectorcall(add_cb, stack, 1, context_kwname);
29542955
Py_DECREF(add_cb);
29552956
Py_DECREF(wrapper);

‎Objects/call.c

Copy file name to clipboardExpand all lines: Objects/call.c
+13-3Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,9 @@ _Py_CheckSlotResult(PyObject *obj, const char *slot_name, int success)
109109
PyObject *
110110
PyObject_CallNoArgs(PyObject *func)
111111
{
112-
return _PyObject_CallNoArgs(func);
112+
EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_API, func);
113+
PyThreadState *tstate = _PyThreadState_GET();
114+
return _PyObject_VectorcallTstate(tstate, func, NULL, 0, NULL);
113115
}
114116

115117

@@ -322,7 +324,7 @@ _PyObject_Call(PyThreadState *tstate, PyObject *callable,
322324
assert(!_PyErr_Occurred(tstate));
323325
assert(PyTuple_Check(args));
324326
assert(kwargs == NULL || PyDict_Check(kwargs));
325-
327+
EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_API, callable);
326328
vectorcallfunc vector_func = _PyVectorcall_Function(callable);
327329
if (vector_func != NULL) {
328330
return _PyVectorcall_Call(tstate, vector_func, callable, args, kwargs);
@@ -367,6 +369,7 @@ PyCFunction_Call(PyObject *callable, PyObject *args, PyObject *kwargs)
367369
PyObject *
368370
PyObject_CallOneArg(PyObject *func, PyObject *arg)
369371
{
372+
EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_API, func);
370373
assert(arg != NULL);
371374
PyObject *_args[2];
372375
PyObject **args = _args + 1; // For PY_VECTORCALL_ARGUMENTS_OFFSET
@@ -389,6 +392,7 @@ _PyFunction_Vectorcall(PyObject *func, PyObject* const* stack,
389392
assert(nargs >= 0);
390393
PyThreadState *tstate = _PyThreadState_GET();
391394
assert(nargs == 0 || stack != NULL);
395+
EVAL_CALL_STAT_INC(EVAL_CALL_FUNCTION_VECTORCALL);
392396
if (((PyCodeObject *)f->func_code)->co_flags & CO_OPTIMIZED) {
393397
return _PyEval_Vector(tstate, f, NULL, stack, nargs, kwnames);
394398
}
@@ -520,7 +524,7 @@ _PyObject_CallFunctionVa(PyThreadState *tstate, PyObject *callable,
520524
if (stack == NULL) {
521525
return NULL;
522526
}
523-
527+
EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_API, callable);
524528
if (nargs == 1 && PyTuple_Check(stack[0])) {
525529
/* Special cases for backward compatibility:
526530
- PyObject_CallFunction(func, "O", tuple) calls func(*tuple)
@@ -815,6 +819,11 @@ object_vacall(PyThreadState *tstate, PyObject *base,
815819
stack[i] = va_arg(vargs, PyObject *);
816820
}
817821

822+
#ifdef Py_STATS
823+
if (PyFunction_Check(callable)) {
824+
EVAL_CALL_STAT_INC(EVAL_CALL_API);
825+
}
826+
#endif
818827
/* Call the function */
819828
result = _PyObject_VectorcallTstate(tstate, callable, stack, nargs, NULL);
820829

@@ -852,6 +861,7 @@ PyObject_VectorcallMethod(PyObject *name, PyObject *const *args,
852861
args++;
853862
nargsf--;
854863
}
864+
EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_METHOD, callable);
855865
PyObject *result = _PyObject_VectorcallTstate(tstate, callable,
856866
args, nargsf, kwnames);
857867
Py_DECREF(callable);

‎Objects/descrobject.c

Copy file name to clipboardExpand all lines: Objects/descrobject.c
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1677,6 +1677,7 @@ property_descr_set(PyObject *self, PyObject *obj, PyObject *value)
16771677
res = PyObject_CallOneArg(func, obj);
16781678
}
16791679
else {
1680+
EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_API, func);
16801681
PyObject *args[] = { obj, value };
16811682
res = PyObject_Vectorcall(func, args, 2, NULL);
16821683
}

‎Objects/genobject.c

Copy file name to clipboardExpand all lines: Objects/genobject.c
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "pycore_pystate.h" // _PyThreadState_GET()
1414
#include "structmember.h" // PyMemberDef
1515
#include "opcode.h" // SEND
16+
#include "pystats.h"
1617

1718
static PyObject *gen_close(PyGenObject *, PyObject *);
1819
static PyObject *async_gen_asend_new(PyAsyncGenObject *, PyObject *);
@@ -218,6 +219,7 @@ gen_send_ex2(PyGenObject *gen, PyObject *arg, PyObject **presult,
218219
}
219220

220221
gen->gi_frame_state = FRAME_EXECUTING;
222+
EVAL_CALL_STAT_INC(EVAL_CALL_GENERATOR);
221223
result = _PyEval_EvalFrame(tstate, frame, exc);
222224
if (gen->gi_frame_state == FRAME_EXECUTING) {
223225
gen->gi_frame_state = FRAME_COMPLETED;

‎Objects/typeobject.c

Copy file name to clipboardExpand all lines: Objects/typeobject.c
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1653,6 +1653,7 @@ vectorcall_unbound(PyThreadState *tstate, int unbound, PyObject *func,
16531653
args++;
16541654
nargsf = nargsf - 1 + PY_VECTORCALL_ARGUMENTS_OFFSET;
16551655
}
1656+
EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_SLOT, func);
16561657
return _PyObject_VectorcallTstate(tstate, func, args, nargsf, NULL);
16571658
}
16581659

‎Python/bltinmodule.c

Copy file name to clipboardExpand all lines: Python/bltinmodule.c
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ builtin___build_class__(PyObject *self, PyObject *const *args, Py_ssize_t nargs,
198198
goto error;
199199
}
200200
PyThreadState *tstate = _PyThreadState_GET();
201+
EVAL_CALL_STAT_INC(EVAL_CALL_BUILD_CLASS);
201202
cell = _PyEval_Vector(tstate, (PyFunctionObject *)func, ns, NULL, 0, NULL);
202203
if (cell != NULL) {
203204
if (bases != orig_bases) {

‎Python/ceval.c

Copy file name to clipboardExpand all lines: Python/ceval.c
+4-1Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1207,6 +1207,7 @@ PyEval_EvalCode(PyObject *co, PyObject *globals, PyObject *locals)
12071207
if (func == NULL) {
12081208
return NULL;
12091209
}
1210+
EVAL_CALL_STAT_INC(EVAL_CALL_LEGACY);
12101211
PyObject *res = _PyEval_Vector(tstate, func, locals, NULL, 0, NULL);
12111212
Py_DECREF(func);
12121213
return res;
@@ -6432,6 +6433,7 @@ _PyEval_Vector(PyThreadState *tstate, PyFunctionObject *func,
64326433
if (frame == NULL) {
64336434
return NULL;
64346435
}
6436+
EVAL_CALL_STAT_INC(EVAL_CALL_VECTOR);
64356437
PyObject *retval = _PyEval_EvalFrame(tstate, frame, 0);
64366438
assert(
64376439
_PyFrame_GetStackPointer(frame) == _PyFrame_Stackbase(frame) ||
@@ -6507,6 +6509,7 @@ PyEval_EvalCodeEx(PyObject *_co, PyObject *globals, PyObject *locals,
65076509
if (func == NULL) {
65086510
goto fail;
65096511
}
6512+
EVAL_CALL_STAT_INC(EVAL_CALL_LEGACY);
65106513
res = _PyEval_Vector(tstate, func, locals,
65116514
allargs, argcount,
65126515
kwnames);
@@ -7299,7 +7302,6 @@ do_call_core(PyThreadState *tstate,
72997302
)
73007303
{
73017304
PyObject *result;
7302-
73037305
if (PyCFunction_CheckExact(func) || PyCMethod_CheckExact(func)) {
73047306
C_TRACE(result, PyObject_Call(func, callargs, kwdict));
73057307
return result;
@@ -7329,6 +7331,7 @@ do_call_core(PyThreadState *tstate,
73297331
return result;
73307332
}
73317333
}
7334+
EVAL_CALL_STAT_INC_IF_FUNCTION(EVAL_CALL_FUNCTION_EX, func);
73327335
return PyObject_Call(func, callargs, kwdict);
73337336
}
73347337

‎Python/specialize.c

Copy file name to clipboardExpand all lines: Python/specialize.c
+4-1Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,9 @@ print_call_stats(FILE *out, CallStats *stats)
176176
fprintf(out, "Calls to Python functions inlined: %" PRIu64 "\n", stats->inlined_py_calls);
177177
fprintf(out, "Frames pushed: %" PRIu64 "\n", stats->frames_pushed);
178178
fprintf(out, "Frame objects created: %" PRIu64 "\n", stats->frame_objects_created);
179+
for (int i = 0; i < EVAL_CALL_KINDS; i++) {
180+
fprintf(out, "Calls via PyEval_EvalFrame[%d] : %" PRIu64 "\n", i, stats->eval_calls[i]);
181+
}
179182
}
180183

181184
static void
@@ -904,7 +907,7 @@ typedef enum {
904907
MANAGED_DICT = 2,
905908
OFFSET_DICT = 3,
906909
NO_DICT = 4,
907-
LAZY_DICT = 5,
910+
LAZY_DICT = 5,
908911
} ObjectDictKind;
909912

910913
// Please collect stats carefully before and after modifying. A subtle change

‎Tools/scripts/summarize_stats.py

Copy file name to clipboardExpand all lines: Tools/scripts/summarize_stats.py
+12-5Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,13 +108,14 @@ def extract_opcode_stats(stats):
108108
opcode_stats[int(n)][rest.strip(".")] = value
109109
return opcode_stats
110110

111-
def parse_kinds(spec_src):
111+
def parse_kinds(spec_src, prefix="SPEC_FAIL"):
112112
defines = collections.defaultdict(list)
113+
start = "#define " + prefix + "_"
113114
for line in spec_src:
114115
line = line.strip()
115-
if not line.startswith("#define SPEC_FAIL_"):
116+
if not line.startswith(start):
116117
continue
117-
line = line[len("#define SPEC_FAIL_"):]
118+
line = line[len(start):]
118119
name, val = line.split()
119120
defines[int(val.strip())].append(name.strip())
120121
return defines
@@ -129,8 +130,6 @@ def kind_to_text(kind, defines, opname):
129130
opname = "ATTR"
130131
if opname.endswith("SUBSCR"):
131132
opname = "SUBSCR"
132-
if opname.startswith("PRECALL"):
133-
opname = "CALL"
134133
for name in defines[kind]:
135134
if name.startswith(opname):
136135
return pretty(name[len(opname)+1:])
@@ -254,6 +253,9 @@ def emit_specialization_overview(opcode_stats, total):
254253
))
255254

256255
def emit_call_stats(stats):
256+
stats_path = os.path.join(os.path.dirname(__file__), "../../Include/pystats.h")
257+
with open(stats_path) as stats_src:
258+
defines = parse_kinds(stats_src, prefix="EVAL_CALL")
257259
with Section("Call stats", summary="Inlined calls and frame stats"):
258260
total = 0
259261
for key, value in stats.items():
@@ -263,6 +265,11 @@ def emit_call_stats(stats):
263265
for key, value in stats.items():
264266
if "Calls to" in key:
265267
rows.append((key, value, f"{100*value/total:0.1f}%"))
268+
elif key.startswith("Calls "):
269+
name, index = key[:-1].split("[")
270+
index = int(index)
271+
label = name + " (" + pretty(defines[index][0]) + ")"
272+
rows.append((label, value, f"{100*value/total:0.1f}%"))
266273
for key, value in stats.items():
267274
if key.startswith("Frame"):
268275
rows.append((key, value, f"{100*value/total:0.1f}%"))

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.