Skip to content

Commit bf4bc36

Browse files
authored
GH-109369: Merge all eval-breaker flags and monitoring version into one word. (GH-109846)
1 parent 7c149a7 commit bf4bc36

13 files changed

+188
-234
lines changed

Include/cpython/code.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ typedef struct {
167167
PyObject *co_weakreflist; /* to support weakrefs to code objects */ \
168168
_PyExecutorArray *co_executors; /* executors from optimizer */ \
169169
_PyCoCached *_co_cached; /* cached co_* attributes */ \
170-
uint64_t _co_instrumentation_version; /* current instrumentation version */ \
170+
uintptr_t _co_instrumentation_version; /* current instrumentation version */ \
171171
_PyCoMonitoringData *_co_monitoring; /* Monitoring data */ \
172172
int _co_firsttraceable; /* index of first traceable instruction */ \
173173
/* Scratch space for extra data relating to the code object. \

Include/internal/pycore_ceval.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,39 @@ int _PyEval_UnpackIterable(PyThreadState *tstate, PyObject *v, int argcnt, int a
193193
void _PyEval_FrameClearAndPop(PyThreadState *tstate, _PyInterpreterFrame *frame);
194194

195195

196+
#define _PY_GIL_DROP_REQUEST_BIT 0
197+
#define _PY_SIGNALS_PENDING_BIT 1
198+
#define _PY_CALLS_TO_DO_BIT 2
199+
#define _PY_ASYNC_EXCEPTION_BIT 3
200+
#define _PY_GC_SCHEDULED_BIT 4
201+
202+
/* Reserve a few bits for future use */
203+
#define _PY_EVAL_EVENTS_BITS 8
204+
#define _PY_EVAL_EVENTS_MASK ((1 << _PY_EVAL_EVENTS_BITS)-1)
205+
206+
static inline void
207+
_Py_set_eval_breaker_bit(PyInterpreterState *interp, uint32_t bit, uint32_t set)
208+
{
209+
assert(set == 0 || set == 1);
210+
uintptr_t to_set = set << bit;
211+
uintptr_t mask = ((uintptr_t)1) << bit;
212+
uintptr_t old = _Py_atomic_load_uintptr(&interp->ceval.eval_breaker);
213+
if ((old & mask) == to_set) {
214+
return;
215+
}
216+
uintptr_t new;
217+
do {
218+
new = (old & ~mask) | to_set;
219+
} while (!_Py_atomic_compare_exchange_uintptr(&interp->ceval.eval_breaker, &old, new));
220+
}
221+
222+
static inline bool
223+
_Py_eval_breaker_bit_is_set(PyInterpreterState *interp, int32_t bit)
224+
{
225+
return _Py_atomic_load_uintptr_relaxed(&interp->ceval.eval_breaker) & (((uintptr_t)1) << bit);
226+
}
227+
228+
196229
#ifdef __cplusplus
197230
}
198231
#endif

Include/internal/pycore_ceval_state.h

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,7 @@ struct _pending_calls {
1717
int busy;
1818
PyThread_type_lock lock;
1919
/* Request for running pending calls. */
20-
_Py_atomic_int calls_to_do;
21-
/* Request for looking at the `async_exc` field of the current
22-
thread state.
23-
Guarded by the GIL. */
24-
int async_exc;
20+
int32_t calls_to_do;
2521
#define NPENDINGCALLS 32
2622
struct _pending_call {
2723
_Py_pending_call_func func;
@@ -62,11 +58,6 @@ struct _ceval_runtime_state {
6258
int _not_used;
6359
#endif
6460
} perf;
65-
/* Request for checking signals. It is shared by all interpreters (see
66-
bpo-40513). Any thread of any interpreter can receive a signal, but only
67-
the main thread of the main interpreter can handle signals: see
68-
_Py_ThreadCanHandleSignals(). */
69-
_Py_atomic_int signals_pending;
7061
/* Pending calls to be made only on the main thread. */
7162
struct _pending_calls pending_mainthread;
7263
};
@@ -87,14 +78,12 @@ struct _ceval_state {
8778
* the fast path in the eval loop.
8879
* It is by far the hottest field in this struct and
8980
* should be placed at the beginning. */
90-
_Py_atomic_int eval_breaker;
91-
/* Request for dropping the GIL */
92-
_Py_atomic_int gil_drop_request;
81+
uintptr_t eval_breaker;
82+
/* Avoid false sharing */
83+
int64_t padding[7];
9384
int recursion_limit;
9485
struct _gil_runtime_state *gil;
9586
int own_gil;
96-
/* The GC is ready to be executed */
97-
_Py_atomic_int gc_scheduled;
9887
struct _pending_calls pending;
9988
};
10089

Include/internal/pycore_interp.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,7 @@ struct _is {
6767
int _initialized;
6868
int finalizing;
6969

70-
uint64_t monitoring_version;
71-
uint64_t last_restart_version;
70+
uintptr_t last_restart_version;
7271
struct pythreads {
7372
uint64_t next_unique_id;
7473
/* The linked list of threads, newest first. */
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
The internal eval_breaker and supporting flags, plus the monitoring version
2+
have been merged into a single atomic integer to speed up checks.

Modules/gcmodule.c

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
*/
2525

2626
#include "Python.h"
27+
#include "pycore_ceval.h" // _Py_set_eval_breaker_bit()
2728
#include "pycore_context.h"
2829
#include "pycore_dict.h" // _PyDict_MaybeUntrack()
2930
#include "pycore_initconfig.h"
@@ -2274,11 +2275,7 @@ _Py_ScheduleGC(PyInterpreterState *interp)
22742275
if (gcstate->collecting == 1) {
22752276
return;
22762277
}
2277-
struct _ceval_state *ceval = &interp->ceval;
2278-
if (!_Py_atomic_load_relaxed(&ceval->gc_scheduled)) {
2279-
_Py_atomic_store_relaxed(&ceval->gc_scheduled, 1);
2280-
_Py_atomic_store_relaxed(&ceval->eval_breaker, 1);
2281-
}
2278+
_Py_set_eval_breaker_bit(interp, _PY_GC_SCHEDULED_BIT, 1);
22822279
}
22832280

22842281
void

Modules/signalmodule.c

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1767,9 +1767,8 @@ PyErr_CheckSignals(void)
17671767
Python code to ensure signals are handled. Checking for the GC here
17681768
allows long running native code to clean cycles created using the C-API
17691769
even if it doesn't run the evaluation loop */
1770-
struct _ceval_state *interp_ceval_state = &tstate->interp->ceval;
1771-
if (_Py_atomic_load_relaxed(&interp_ceval_state->gc_scheduled)) {
1772-
_Py_atomic_store_relaxed(&interp_ceval_state->gc_scheduled, 0);
1770+
if (_Py_eval_breaker_bit_is_set(tstate->interp, _PY_GC_SCHEDULED_BIT)) {
1771+
_Py_set_eval_breaker_bit(tstate->interp, _PY_GC_SCHEDULED_BIT, 0);
17731772
_Py_RunGC(tstate);
17741773
}
17751774

Python/bytecodes.c

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,12 @@ dummy_func(
136136
inst(RESUME, (--)) {
137137
TIER_ONE_ONLY
138138
assert(frame == tstate->current_frame);
139-
if (_PyFrame_GetCode(frame)->_co_instrumentation_version != tstate->interp->monitoring_version) {
139+
uintptr_t global_version =
140+
_Py_atomic_load_uintptr_relaxed(&tstate->interp->ceval.eval_breaker) &
141+
~_PY_EVAL_EVENTS_MASK;
142+
uintptr_t code_version = _PyFrame_GetCode(frame)->_co_instrumentation_version;
143+
assert((code_version & 255) == 0);
144+
if (code_version != global_version) {
140145
int err = _Py_Instrument(_PyFrame_GetCode(frame), tstate->interp);
141146
ERROR_IF(err, error);
142147
next_instr--;
@@ -154,17 +159,16 @@ dummy_func(
154159
DEOPT_IF(_Py_emscripten_signal_clock == 0);
155160
_Py_emscripten_signal_clock -= Py_EMSCRIPTEN_SIGNAL_HANDLING;
156161
#endif
157-
/* Possibly combine these two checks */
158-
DEOPT_IF(_PyFrame_GetCode(frame)->_co_instrumentation_version != tstate->interp->monitoring_version);
159-
DEOPT_IF(_Py_atomic_load_relaxed_int32(&tstate->interp->ceval.eval_breaker));
162+
uintptr_t eval_breaker = _Py_atomic_load_uintptr_relaxed(&tstate->interp->ceval.eval_breaker);
163+
uintptr_t version = _PyFrame_GetCode(frame)->_co_instrumentation_version;
164+
assert((version & _PY_EVAL_EVENTS_MASK) == 0);
165+
DEOPT_IF(eval_breaker != version);
160166
}
161167

162168
inst(INSTRUMENTED_RESUME, (--)) {
163-
/* Possible performance enhancement:
164-
* We need to check the eval breaker anyway, can we
165-
* combine the instrument verison check and the eval breaker test?
166-
*/
167-
if (_PyFrame_GetCode(frame)->_co_instrumentation_version != tstate->interp->monitoring_version) {
169+
uintptr_t global_version = _Py_atomic_load_uintptr_relaxed(&tstate->interp->ceval.eval_breaker) & ~_PY_EVAL_EVENTS_MASK;
170+
uintptr_t code_version = _PyFrame_GetCode(frame)->_co_instrumentation_version;
171+
if (code_version != global_version) {
168172
if (_Py_Instrument(_PyFrame_GetCode(frame), tstate->interp)) {
169173
goto error;
170174
}

0 commit comments

Comments
 (0)