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.
This commit is contained in:
Victor Stinner 2020-03-20 14:50:35 +01:00 committed by GitHub
parent d2a8e5b42c
commit d83168854e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 22 additions and 6 deletions

View File

@ -317,12 +317,18 @@ _Py_IsMainInterpreter(PyThreadState* tstate)
static inline int
_Py_ThreadCanHandleSignals(PyThreadState *tstate)
{
/* Use directly _PyRuntime rather than tstate->interp->runtime, since
this function is used in performance critical code path (ceval) */
return (_Py_IsMainThread() && _Py_IsMainInterpreter(tstate));
}
/* Only execute pending calls on the main thread. */
static inline int
_Py_ThreadCanHandlePendingCalls(void)
{
return _Py_IsMainThread();
}
/* Variable and macro for in-line access to current thread
and interpreter state */

View File

@ -0,0 +1,8 @@
Optimize pending calls in multithreaded applications. If a thread different
than the main thread schedules a pending call (:c:func:`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.

View File

@ -149,7 +149,8 @@ COMPUTE_EVAL_BREAKER(PyThreadState *tstate,
_Py_atomic_load_relaxed(&ceval->gil_drop_request)
| (_Py_atomic_load_relaxed(&ceval->signals_pending)
&& _Py_ThreadCanHandleSignals(tstate))
| _Py_atomic_load_relaxed(&ceval2->pending.calls_to_do)
| (_Py_atomic_load_relaxed(&ceval2->pending.calls_to_do)
&& _Py_ThreadCanHandlePendingCalls())
| ceval2->pending.async_exc);
}
@ -180,9 +181,10 @@ static inline void
SIGNAL_PENDING_CALLS(PyThreadState *tstate)
{
assert(is_tstate_valid(tstate));
struct _ceval_runtime_state *ceval = &tstate->interp->runtime->ceval;
struct _ceval_state *ceval2 = &tstate->interp->ceval;
_Py_atomic_store_relaxed(&ceval2->pending.calls_to_do, 1);
_Py_atomic_store_relaxed(&ceval2->eval_breaker, 1);
COMPUTE_EVAL_BREAKER(tstate, ceval, ceval2);
}
@ -606,8 +608,8 @@ handle_signals(PyThreadState *tstate)
static int
make_pending_calls(PyThreadState *tstate)
{
/* only service pending calls on main thread */
if (!_Py_IsMainThread()) {
/* only execute pending calls on main thread */
if (!_Py_ThreadCanHandlePendingCalls()) {
return 0;
}