Skip to content

Commit d831688

Browse files
authored
bpo-40010: Optimize pending calls in multithreaded applications (GH-19091)
If a thread different than the main thread schedules a pending call (Py_AddPendingCall()), the bytecode evaluation loop is no longer interrupted at each bytecode instruction to check for pending calls which cannot be executed. Only the main thread can execute pending calls. Previously, the bytecode evaluation loop was interrupted at each instruction until the main thread executes pending calls. * Add _Py_ThreadCanHandlePendingCalls() function. * SIGNAL_PENDING_CALLS() now only sets eval_breaker to 1 if the current thread can execute pending calls. Only the main thread can execute pending calls.
1 parent d2a8e5b commit d831688

File tree

3 files changed

+22
-6
lines changed

3 files changed

+22
-6
lines changed

Include/internal/pycore_pystate.h

+8-2
Original file line numberDiff line numberDiff line change
@@ -317,12 +317,18 @@ _Py_IsMainInterpreter(PyThreadState* tstate)
317317
static inline int
318318
_Py_ThreadCanHandleSignals(PyThreadState *tstate)
319319
{
320-
/* Use directly _PyRuntime rather than tstate->interp->runtime, since
321-
this function is used in performance critical code path (ceval) */
322320
return (_Py_IsMainThread() && _Py_IsMainInterpreter(tstate));
323321
}
324322

325323

324+
/* Only execute pending calls on the main thread. */
325+
static inline int
326+
_Py_ThreadCanHandlePendingCalls(void)
327+
{
328+
return _Py_IsMainThread();
329+
}
330+
331+
326332
/* Variable and macro for in-line access to current thread
327333
and interpreter state */
328334

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Optimize pending calls in multithreaded applications. If a thread different
2+
than the main thread schedules a pending call (:c:func:`Py_AddPendingCall`),
3+
the bytecode evaluation loop is no longer interrupted at each bytecode
4+
instruction to check for pending calls which cannot be executed. Only the
5+
main thread can execute pending calls.
6+
7+
Previously, the bytecode evaluation loop was interrupted at each instruction
8+
until the main thread executes pending calls.

Python/ceval.c

+6-4
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,8 @@ COMPUTE_EVAL_BREAKER(PyThreadState *tstate,
149149
_Py_atomic_load_relaxed(&ceval->gil_drop_request)
150150
| (_Py_atomic_load_relaxed(&ceval->signals_pending)
151151
&& _Py_ThreadCanHandleSignals(tstate))
152-
| _Py_atomic_load_relaxed(&ceval2->pending.calls_to_do)
152+
| (_Py_atomic_load_relaxed(&ceval2->pending.calls_to_do)
153+
&& _Py_ThreadCanHandlePendingCalls())
153154
| ceval2->pending.async_exc);
154155
}
155156

@@ -180,9 +181,10 @@ static inline void
180181
SIGNAL_PENDING_CALLS(PyThreadState *tstate)
181182
{
182183
assert(is_tstate_valid(tstate));
184+
struct _ceval_runtime_state *ceval = &tstate->interp->runtime->ceval;
183185
struct _ceval_state *ceval2 = &tstate->interp->ceval;
184186
_Py_atomic_store_relaxed(&ceval2->pending.calls_to_do, 1);
185-
_Py_atomic_store_relaxed(&ceval2->eval_breaker, 1);
187+
COMPUTE_EVAL_BREAKER(tstate, ceval, ceval2);
186188
}
187189

188190

@@ -606,8 +608,8 @@ handle_signals(PyThreadState *tstate)
606608
static int
607609
make_pending_calls(PyThreadState *tstate)
608610
{
609-
/* only service pending calls on main thread */
610-
if (!_Py_IsMainThread()) {
611+
/* only execute pending calls on main thread */
612+
if (!_Py_ThreadCanHandlePendingCalls()) {
611613
return 0;
612614
}
613615

0 commit comments

Comments
 (0)