Skip to content

Commit 37c3544

Browse files
committed
gh-99633: Inline independent coroutine check
The functions `_PyGen_ActivateContext` and `_PyGen_DeactivateContext` are called every time a value or exception is sent to a coroutine. These functions are no-ops for dependent coroutines (coroutines without their own independent context stack). Coroutines are dependent by default, and the vast majority of performance-sensitive coroutines are expected to be dependent, so move the check that determines whether the coroutine is dependent or independent to an inline function to speed up send calls.
1 parent d08b88d commit 37c3544

File tree

2 files changed

+35
-16
lines changed

2 files changed

+35
-16
lines changed

Include/internal/pycore_context.h

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77

88
#include "cpython/context.h"
99
#include "cpython/genobject.h" // PyGenObject
10+
#include "pycore_genobject.h" // PyGenObject
1011
#include "pycore_hamt.h" // PyHamtObject
12+
#include "pycore_tstate.h" // _PyThreadStateImpl
1113

1214
#define CONTEXT_MAX_WATCHERS 8
1315

@@ -48,6 +50,9 @@ struct _pycontextobject {
4850
// (if non-NULL) is immediately entered.
4951
int _PyGen_ResetContext(PyThreadState *ts, PyGenObject *self, PyObject *ctx);
5052

53+
void _PyGen_ActivateContextImpl(_PyThreadStateImpl *tsi, PyGenObject *self);
54+
void _PyGen_DeactivateContextImpl(_PyThreadStateImpl *tsi, PyGenObject *self);
55+
5156
// Makes the given coroutine's context stack the active context stack for the
5257
// thread, shadowing (temporarily deactivating) the thread's previously active
5358
// context stack. The context stack remains active until deactivated with a
@@ -59,7 +64,19 @@ int _PyGen_ResetContext(PyThreadState *ts, PyGenObject *self, PyObject *ctx);
5964
// until deactivated.
6065
//
6166
// If the coroutine's context stack is empty this function has no effect.
62-
void _PyGen_ActivateContext(PyThreadState *ts, PyGenObject *self);
67+
//
68+
// This is called each time a value is sent to a coroutine, so it is inlined to
69+
// avoid function call overhead in the common case of dependent coroutines.
70+
static inline void
71+
_PyGen_ActivateContext(PyThreadState *ts, PyGenObject *self)
72+
{
73+
_PyThreadStateImpl *tsi = (_PyThreadStateImpl *)ts;
74+
assert(self->_ctx_chain.prev == NULL);
75+
if (self->_ctx_chain.ctx == NULL) {
76+
return;
77+
}
78+
_PyGen_ActivateContextImpl(tsi, self);
79+
}
6380

6481
// Deactivates the given coroutine's context stack, un-shadowing (reactivating)
6582
// the thread's previously active context stack. Does not affect any contexts
@@ -70,7 +87,21 @@ void _PyGen_ActivateContext(PyThreadState *ts, PyGenObject *self);
7087
//
7188
// If the coroutine's context stack is not the active context stack this
7289
// function has no effect.
73-
void _PyGen_DeactivateContext(PyThreadState *ts, PyGenObject *self);
90+
//
91+
// This is called each time a value is sent to a coroutine, so it is inlined to
92+
// avoid function call overhead in the common case of dependent coroutines.
93+
static inline void
94+
_PyGen_DeactivateContext(PyThreadState *ts, PyGenObject *self)
95+
{
96+
_PyThreadStateImpl *tsi = (_PyThreadStateImpl *)ts;
97+
if (tsi->_ctx_chain.prev != &self->_ctx_chain) {
98+
assert(self->_ctx_chain.ctx == NULL);
99+
assert(self->_ctx_chain.prev == NULL);
100+
return;
101+
}
102+
assert(self->_ctx_chain.ctx != NULL);
103+
_PyGen_DeactivateContextImpl(tsi, self);
104+
}
74105

75106

76107
struct _pycontextvarobject {

Python/context.c

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -404,27 +404,15 @@ _PyGen_ResetContext(PyThreadState *ts, PyGenObject *self, PyObject *ctx)
404404
}
405405

406406
void
407-
_PyGen_ActivateContext(PyThreadState *ts, PyGenObject *self)
407+
_PyGen_ActivateContextImpl(_PyThreadStateImpl *tsi, PyGenObject *self)
408408
{
409-
_PyThreadStateImpl *tsi = (_PyThreadStateImpl *)ts;
410-
assert(self->_ctx_chain.prev == NULL);
411-
if (self->_ctx_chain.ctx == NULL) {
412-
return;
413-
}
414409
contextchain_link(&self->_ctx_chain, &tsi->_ctx_chain);
415410
context_switched(tsi);
416411
}
417412

418413
void
419-
_PyGen_DeactivateContext(PyThreadState *ts, PyGenObject *self)
414+
_PyGen_DeactivateContextImpl(_PyThreadStateImpl *tsi, PyGenObject *self)
420415
{
421-
_PyThreadStateImpl *tsi = (_PyThreadStateImpl *)ts;
422-
if (tsi->_ctx_chain.prev != &self->_ctx_chain) {
423-
assert(self->_ctx_chain.ctx == NULL);
424-
assert(self->_ctx_chain.prev == NULL);
425-
return;
426-
}
427-
assert(self->_ctx_chain.ctx != NULL);
428416
contextchain_unlink(&self->_ctx_chain, &tsi->_ctx_chain);
429417
context_switched(tsi);
430418
}

0 commit comments

Comments
 (0)