Skip to content

Commit

Permalink
pythongh-114203: Optimise simple recursive critical sections (python#…
Browse files Browse the repository at this point in the history
…128126)

Add a fast path to (single-mutex) critical section locking _iff_ the mutex
is already held by the currently active, top-most critical section of this
thread. This can matter a lot for indirectly recursive critical sections
without intervening critical sections.
  • Loading branch information
Yhg1s authored Dec 23, 2024
1 parent 831b6de commit 180d417
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 7 deletions.
14 changes: 14 additions & 0 deletions Include/internal/pycore_critical_section.h
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,12 @@ _PyCriticalSection_Pop(PyCriticalSection *c)
static inline void
_PyCriticalSection_End(PyCriticalSection *c)
{
// If the mutex is NULL, we used the fast path in
// _PyCriticalSection_BeginSlow for locks already held in the top-most
// critical section, and we shouldn't unlock or pop this critical section.
if (c->_cs_mutex == NULL) {
return;
}
PyMutex_Unlock(c->_cs_mutex);
_PyCriticalSection_Pop(c);
}
Expand Down Expand Up @@ -199,6 +205,14 @@ _PyCriticalSection2_Begin(PyCriticalSection2 *c, PyObject *a, PyObject *b)
static inline void
_PyCriticalSection2_End(PyCriticalSection2 *c)
{
// if mutex1 is NULL, we used the fast path in
// _PyCriticalSection_BeginSlow for mutexes that are already held,
// which should only happen when mutex1 and mutex2 were the same mutex,
// and mutex2 should also be NULL.
if (c->_cs_base._cs_mutex == NULL) {
assert(c->_cs_mutex2 == NULL);
return;
}
if (c->_cs_mutex2) {
PyMutex_Unlock(c->_cs_mutex2);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Optimize ``Py_BEGIN_CRITICAL_SECTION`` for simple recursive calls.
24 changes: 17 additions & 7 deletions Python/critical_section.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,28 @@ static_assert(_Alignof(PyCriticalSection) >= 4,
"critical section must be aligned to at least 4 bytes");
#endif

#ifdef Py_GIL_DISABLED
static PyCriticalSection *
untag_critical_section(uintptr_t tag)
{
return (PyCriticalSection *)(tag & ~_Py_CRITICAL_SECTION_MASK);
}
#endif

void
_PyCriticalSection_BeginSlow(PyCriticalSection *c, PyMutex *m)
{
#ifdef Py_GIL_DISABLED
PyThreadState *tstate = _PyThreadState_GET();
// As an optimisation for locking the same object recursively, skip
// locking if the mutex is currently locked by the top-most critical
// section.
if (tstate->critical_section &&
untag_critical_section(tstate->critical_section)->_cs_mutex == m) {
c->_cs_mutex = NULL;
c->_cs_prev = 0;
return;
}
c->_cs_mutex = NULL;
c->_cs_prev = (uintptr_t)tstate->critical_section;
tstate->critical_section = (uintptr_t)c;
Expand Down Expand Up @@ -42,13 +59,6 @@ _PyCriticalSection2_BeginSlow(PyCriticalSection2 *c, PyMutex *m1, PyMutex *m2,
#endif
}

#ifdef Py_GIL_DISABLED
static PyCriticalSection *
untag_critical_section(uintptr_t tag)
{
return (PyCriticalSection *)(tag & ~_Py_CRITICAL_SECTION_MASK);
}
#endif

// Release all locks held by critical sections. This is called by
// _PyThreadState_Detach.
Expand Down

0 comments on commit 180d417

Please sign in to comment.