Skip to content

Commit 9e7b207

Browse files
authored
bpo-43760: Speed up check for tracing in interpreter dispatch (#25276)
* Remove redundant tracing_possible field from interpreter state. * Move 'use_tracing' from tstate onto C stack, for fastest possible checking in dispatch logic. * Add comments stressing the importance stack discipline when dealing with CFrames. * Add NEWS
1 parent c2b7a66 commit 9e7b207

File tree

6 files changed

+61
-35
lines changed

6 files changed

+61
-35
lines changed

Include/cpython/pystate.h

+21-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,21 @@ typedef int (*Py_tracefunc)(PyObject *, PyFrameObject *, int, PyObject *);
2929
#define PyTrace_OPCODE 7
3030

3131

32+
typedef struct _cframe {
33+
/* This struct will be threaded through the C stack
34+
* allowing fast access to per-thread state that needs
35+
* to be accessed quickly by the interpreter, but can
36+
* be modified outside of the interpreter.
37+
*
38+
* WARNING: This makes data on the C stack accessible from
39+
* heap objects. Care must be taken to maintain stack
40+
* discipline and make sure that instances of this struct cannot
41+
* accessed outside of their lifetime.
42+
*/
43+
int use_tracing;
44+
struct _cframe *previous;
45+
} CFrame;
46+
3247
typedef struct _err_stackitem {
3348
/* This struct represents an entry on the exception stack, which is a
3449
* per-coroutine state. (Coroutine in the computer science sense,
@@ -61,7 +76,10 @@ struct _ts {
6176
This is to prevent the actual trace/profile code from being recorded in
6277
the trace/profile. */
6378
int tracing;
64-
int use_tracing;
79+
80+
/* Pointer to current CFrame in the C stack frame of the currently,
81+
* or most recently, executing _PyEval_EvalFrameDefault. */
82+
CFrame *cframe;
6583

6684
Py_tracefunc c_profilefunc;
6785
Py_tracefunc c_tracefunc;
@@ -129,6 +147,8 @@ struct _ts {
129147
/* Unique thread state id. */
130148
uint64_t id;
131149

150+
CFrame root_cframe;
151+
132152
/* XXX signal handlers should also be here */
133153

134154
};

Include/internal/pycore_interp.h

-6
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,6 @@ struct _pending_calls {
3333

3434
struct _ceval_state {
3535
int recursion_limit;
36-
/* Records whether tracing is on for any thread. Counts the number
37-
of threads for which tstate->c_tracefunc is non-NULL, so if the
38-
value is 0, we know we don't have to check this thread's
39-
c_tracefunc. This speeds up the if statement in
40-
_PyEval_EvalFrameDefault() after fast_next_opcode. */
41-
int tracing_possible;
4236
/* This single variable consolidates all requests to break out of
4337
the fast path in the eval loop. */
4438
_Py_atomic_int eval_breaker;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Move the flag for checking whether tracing is enabled to the C stack, from the heap.
2+
Should speed up dispatch in the interpreter.

Python/ceval.c

+32-23
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ typedef struct {
3737
PyCodeObject *code; // The code object for the bounds. May be NULL.
3838
int instr_prev; // Only valid if code != NULL.
3939
PyCodeAddressRange bounds; // Only valid if code != NULL.
40+
CFrame cframe;
4041
} PyTraceInfo;
4142

4243

@@ -1109,8 +1110,6 @@ match_class(PyThreadState *tstate, PyObject *subject, PyObject *type,
11091110
static int do_raise(PyThreadState *tstate, PyObject *exc, PyObject *cause);
11101111
static int unpack_iterable(PyThreadState *, PyObject *, int, int, PyObject **);
11111112

1112-
#define _Py_TracingPossible(ceval) ((ceval)->tracing_possible)
1113-
11141113

11151114
PyObject *
11161115
PyEval_EvalCode(PyObject *co, PyObject *globals, PyObject *locals)
@@ -1307,7 +1306,7 @@ eval_frame_handle_pending(PyThreadState *tstate)
13071306

13081307
#define DISPATCH() \
13091308
{ \
1310-
if (_Py_TracingPossible(ceval2) OR_DTRACE_LINE OR_LLTRACE) { \
1309+
if (trace_info.cframe.use_tracing OR_DTRACE_LINE OR_LLTRACE) { \
13111310
goto tracing_dispatch; \
13121311
} \
13131312
f->f_lasti = INSTR_OFFSET(); \
@@ -1595,8 +1594,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
15951594
int oparg; /* Current opcode argument, if any */
15961595
PyObject **fastlocals, **freevars;
15971596
PyObject *retval = NULL; /* Return value */
1598-
struct _ceval_state * const ceval2 = &tstate->interp->ceval;
1599-
_Py_atomic_int * const eval_breaker = &ceval2->eval_breaker;
1597+
_Py_atomic_int * const eval_breaker = &tstate->interp->ceval.eval_breaker;
16001598
PyCodeObject *co;
16011599

16021600
const _Py_CODEUNIT *first_instr;
@@ -1616,11 +1614,20 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
16161614
/* Mark trace_info as uninitialized */
16171615
trace_info.code = NULL;
16181616

1617+
/* WARNING: Because the CFrame lives on the C stack,
1618+
* but can be accessed from a heap allocated object (tstate)
1619+
* strict stack discipline must be maintained.
1620+
*/
1621+
CFrame *prev_cframe = tstate->cframe;
1622+
trace_info.cframe.use_tracing = prev_cframe->use_tracing;
1623+
trace_info.cframe.previous = prev_cframe;
1624+
tstate->cframe = &trace_info.cframe;
1625+
16191626
/* push frame */
16201627
tstate->frame = f;
16211628
co = f->f_code;
16221629

1623-
if (tstate->use_tracing) {
1630+
if (trace_info.cframe.use_tracing) {
16241631
if (tstate->c_tracefunc != NULL) {
16251632
/* tstate->c_tracefunc, if defined, is a
16261633
function that will be called on *every* entry
@@ -1782,7 +1789,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
17821789

17831790
/* line-by-line tracing support */
17841791

1785-
if (_Py_TracingPossible(ceval2) &&
1792+
if (trace_info.cframe.use_tracing &&
17861793
tstate->c_tracefunc != NULL && !tstate->tracing) {
17871794
int err;
17881795
/* see maybe_call_line_trace()
@@ -4543,7 +4550,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
45434550
PUSH(val);
45444551
PUSH(exc);
45454552
JUMPTO(handler);
4546-
if (_Py_TracingPossible(ceval2)) {
4553+
if (trace_info.cframe.use_tracing) {
45474554
trace_info.instr_prev = INT_MAX;
45484555
}
45494556
/* Resume normal execution */
@@ -4567,7 +4574,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
45674574
f->f_stackdepth = 0;
45684575
f->f_state = FRAME_RAISED;
45694576
exiting:
4570-
if (tstate->use_tracing) {
4577+
if (trace_info.cframe.use_tracing) {
45714578
if (tstate->c_tracefunc) {
45724579
if (call_trace_protected(tstate->c_tracefunc, tstate->c_traceobj,
45734580
tstate, f, &trace_info, PyTrace_RETURN, retval)) {
@@ -4584,6 +4591,10 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, PyFrameObject *f, int throwflag)
45844591

45854592
/* pop frame */
45864593
exit_eval_frame:
4594+
/* Restore previous cframe */
4595+
tstate->cframe = trace_info.cframe.previous;
4596+
tstate->cframe->use_tracing = trace_info.cframe.use_tracing;
4597+
45874598
if (PyDTrace_FUNCTION_RETURN_ENABLED())
45884599
dtrace_function_return(f);
45894600
_Py_LeaveRecursiveCall(tstate);
@@ -5507,7 +5518,7 @@ call_trace(Py_tracefunc func, PyObject *obj,
55075518
if (tstate->tracing)
55085519
return 0;
55095520
tstate->tracing++;
5510-
tstate->use_tracing = 0;
5521+
tstate->cframe->use_tracing = 0;
55115522
if (frame->f_lasti < 0) {
55125523
frame->f_lineno = frame->f_code->co_firstlineno;
55135524
}
@@ -5517,7 +5528,7 @@ call_trace(Py_tracefunc func, PyObject *obj,
55175528
}
55185529
result = func(obj, frame, what, arg);
55195530
frame->f_lineno = 0;
5520-
tstate->use_tracing = ((tstate->c_tracefunc != NULL)
5531+
tstate->cframe->use_tracing = ((tstate->c_tracefunc != NULL)
55215532
|| (tstate->c_profilefunc != NULL));
55225533
tstate->tracing--;
55235534
return result;
@@ -5528,15 +5539,15 @@ _PyEval_CallTracing(PyObject *func, PyObject *args)
55285539
{
55295540
PyThreadState *tstate = _PyThreadState_GET();
55305541
int save_tracing = tstate->tracing;
5531-
int save_use_tracing = tstate->use_tracing;
5542+
int save_use_tracing = tstate->cframe->use_tracing;
55325543
PyObject *result;
55335544

55345545
tstate->tracing = 0;
5535-
tstate->use_tracing = ((tstate->c_tracefunc != NULL)
5546+
tstate->cframe->use_tracing = ((tstate->c_tracefunc != NULL)
55365547
|| (tstate->c_profilefunc != NULL));
55375548
result = PyObject_Call(func, args, NULL);
55385549
tstate->tracing = save_tracing;
5539-
tstate->use_tracing = save_use_tracing;
5550+
tstate->cframe->use_tracing = save_use_tracing;
55405551
return result;
55415552
}
55425553

@@ -5590,15 +5601,15 @@ _PyEval_SetProfile(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
55905601
tstate->c_profilefunc = NULL;
55915602
tstate->c_profileobj = NULL;
55925603
/* Must make sure that tracing is not ignored if 'profileobj' is freed */
5593-
tstate->use_tracing = tstate->c_tracefunc != NULL;
5604+
tstate->cframe->use_tracing = tstate->c_tracefunc != NULL;
55945605
Py_XDECREF(profileobj);
55955606

55965607
Py_XINCREF(arg);
55975608
tstate->c_profileobj = arg;
55985609
tstate->c_profilefunc = func;
55995610

56005611
/* Flag that tracing or profiling is turned on */
5601-
tstate->use_tracing = (func != NULL) || (tstate->c_tracefunc != NULL);
5612+
tstate->cframe->use_tracing = (func != NULL) || (tstate->c_tracefunc != NULL);
56025613
return 0;
56035614
}
56045615

@@ -5626,22 +5637,20 @@ _PyEval_SetTrace(PyThreadState *tstate, Py_tracefunc func, PyObject *arg)
56265637
return -1;
56275638
}
56285639

5629-
struct _ceval_state *ceval2 = &tstate->interp->ceval;
56305640
PyObject *traceobj = tstate->c_traceobj;
5631-
ceval2->tracing_possible += (func != NULL) - (tstate->c_tracefunc != NULL);
56325641

56335642
tstate->c_tracefunc = NULL;
56345643
tstate->c_traceobj = NULL;
56355644
/* Must make sure that profiling is not ignored if 'traceobj' is freed */
5636-
tstate->use_tracing = (tstate->c_profilefunc != NULL);
5645+
tstate->cframe->use_tracing = (tstate->c_profilefunc != NULL);
56375646
Py_XDECREF(traceobj);
56385647

56395648
Py_XINCREF(arg);
56405649
tstate->c_traceobj = arg;
56415650
tstate->c_tracefunc = func;
56425651

56435652
/* Flag that tracing or profiling is turned on */
5644-
tstate->use_tracing = ((func != NULL)
5653+
tstate->cframe->use_tracing = ((func != NULL)
56455654
|| (tstate->c_profilefunc != NULL));
56465655

56475656
return 0;
@@ -5836,7 +5845,7 @@ PyEval_GetFuncDesc(PyObject *func)
58365845
}
58375846

58385847
#define C_TRACE(x, call) \
5839-
if (tstate->use_tracing && tstate->c_profilefunc) { \
5848+
if (trace_info->cframe.use_tracing && tstate->c_profilefunc) { \
58405849
if (call_trace(tstate->c_profilefunc, tstate->c_profileobj, \
58415850
tstate, tstate->frame, trace_info, \
58425851
PyTrace_C_CALL, func)) { \
@@ -5917,7 +5926,7 @@ call_function(PyThreadState *tstate,
59175926
Py_ssize_t nargs = oparg - nkwargs;
59185927
PyObject **stack = (*pp_stack) - nargs - nkwargs;
59195928

5920-
if (tstate->use_tracing) {
5929+
if (trace_info->cframe.use_tracing) {
59215930
x = trace_call_function(tstate, trace_info, func, stack, nargs, kwnames);
59225931
}
59235932
else {
@@ -5950,7 +5959,7 @@ do_call_core(PyThreadState *tstate,
59505959
}
59515960
else if (Py_IS_TYPE(func, &PyMethodDescr_Type)) {
59525961
Py_ssize_t nargs = PyTuple_GET_SIZE(callargs);
5953-
if (nargs > 0 && tstate->use_tracing) {
5962+
if (nargs > 0 && trace_info->cframe.use_tracing) {
59545963
/* We need to create a temporary bound method as argument
59555964
for profiling.
59565965

Python/pystate.c

+2-1
Original file line numberDiff line numberDiff line change
@@ -624,7 +624,8 @@ new_threadstate(PyInterpreterState *interp, int init)
624624
tstate->recursion_headroom = 0;
625625
tstate->stackcheck_counter = 0;
626626
tstate->tracing = 0;
627-
tstate->use_tracing = 0;
627+
tstate->root_cframe.use_tracing = 0;
628+
tstate->cframe = &tstate->root_cframe;
628629
tstate->gilstate_counter = 0;
629630
tstate->async_exc = NULL;
630631
tstate->thread_id = PyThread_get_thread_ident();

Python/sysmodule.c

+4-4
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ sys_audit_tstate(PyThreadState *ts, const char *event,
252252

253253
/* Disallow tracing in hooks unless explicitly enabled */
254254
ts->tracing++;
255-
ts->use_tracing = 0;
255+
ts->cframe->use_tracing = 0;
256256
while ((hook = PyIter_Next(hooks)) != NULL) {
257257
_Py_IDENTIFIER(__cantrace__);
258258
PyObject *o;
@@ -265,22 +265,22 @@ sys_audit_tstate(PyThreadState *ts, const char *event,
265265
break;
266266
}
267267
if (canTrace) {
268-
ts->use_tracing = (ts->c_tracefunc || ts->c_profilefunc);
268+
ts->cframe->use_tracing = (ts->c_tracefunc || ts->c_profilefunc);
269269
ts->tracing--;
270270
}
271271
PyObject* args[2] = {eventName, eventArgs};
272272
o = _PyObject_FastCallTstate(ts, hook, args, 2);
273273
if (canTrace) {
274274
ts->tracing++;
275-
ts->use_tracing = 0;
275+
ts->cframe->use_tracing = 0;
276276
}
277277
if (!o) {
278278
break;
279279
}
280280
Py_DECREF(o);
281281
Py_CLEAR(hook);
282282
}
283-
ts->use_tracing = (ts->c_tracefunc || ts->c_profilefunc);
283+
ts->cframe->use_tracing = (ts->c_tracefunc || ts->c_profilefunc);
284284
ts->tracing--;
285285
if (_PyErr_Occurred(ts)) {
286286
goto exit;

0 commit comments

Comments
 (0)