From 49be9cb64f3ce85146213b54589ca686fc6c9752 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 20 Mar 2020 13:48:52 +0100 Subject: [PATCH] bpo-40010: Optimize pending calls in multithreaded applications 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. --- Include/internal/pycore_pystate.h | 10 ++++++++-- .../2020-03-20-13-51-55.bpo-40010.QGf5s8.rst | 8 ++++++++ Python/ceval.c | 10 ++++++---- 3 files changed, 22 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2020-03-20-13-51-55.bpo-40010.QGf5s8.rst diff --git a/Include/internal/pycore_pystate.h b/Include/internal/pycore_pystate.h index da034e185a0175..0073e205bea96f 100644 --- a/Include/internal/pycore_pystate.h +++ b/Include/internal/pycore_pystate.h @@ -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 */ diff --git a/Misc/NEWS.d/next/Core and Builtins/2020-03-20-13-51-55.bpo-40010.QGf5s8.rst b/Misc/NEWS.d/next/Core and Builtins/2020-03-20-13-51-55.bpo-40010.QGf5s8.rst new file mode 100644 index 00000000000000..b0995134ed9b03 --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2020-03-20-13-51-55.bpo-40010.QGf5s8.rst @@ -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. diff --git a/Python/ceval.c b/Python/ceval.c index 86aa2253899aa7..c80ee4b463cf7f 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -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; }