Skip to content

Commit ddb794a

Browse files
committed
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.
1 parent 78ffba4 commit ddb794a

File tree

1 file changed

+25
-1
lines changed

1 file changed

+25
-1
lines changed

Include/internal/pycore_critical_section.h

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,18 @@ _PyCriticalSection_IsActive(uintptr_t tag)
109109
static inline void
110110
_PyCriticalSection_BeginMutex(PyCriticalSection *c, PyMutex *m)
111111
{
112+
// As an optimisation for locking the same object recursively, skip
113+
// locking if the mutex is currently locked and the topmost, active,
114+
// single critical section.
115+
PyThreadState *tstate = _PyThreadState_GET();
116+
if (tstate->critical_section &&
117+
!(tstate->critical_section & _Py_CRITICAL_SECTION_MASK) &&
118+
((PyCriticalSection *)tstate->critical_section)->_cs_mutex == m) {
119+
c->_cs_mutex = NULL;
120+
c->_cs_prev = 0;
121+
return;
122+
}
112123
if (PyMutex_LockFast(m)) {
113-
PyThreadState *tstate = _PyThreadState_GET();
114124
c->_cs_mutex = m;
115125
c->_cs_prev = tstate->critical_section;
116126
tstate->critical_section = (uintptr_t)c;
@@ -145,6 +155,12 @@ _PyCriticalSection_Pop(PyCriticalSection *c)
145155
static inline void
146156
_PyCriticalSection_End(PyCriticalSection *c)
147157
{
158+
// If the mutex is NULL, we used the fast path in
159+
// _PyCriticalSection_BeginMutex for locks already held in the top-most
160+
// critical section, and we shouldn't unlock or pop this critical section.
161+
if (c->_cs_mutex == NULL) {
162+
return;
163+
}
148164
PyMutex_Unlock(c->_cs_mutex);
149165
_PyCriticalSection_Pop(c);
150166
}
@@ -199,6 +215,14 @@ _PyCriticalSection2_Begin(PyCriticalSection2 *c, PyObject *a, PyObject *b)
199215
static inline void
200216
_PyCriticalSection2_End(PyCriticalSection2 *c)
201217
{
218+
// if mutex1 is NULL, we used the fast path in
219+
// _PyCriticalSection_BeginMutex for mutexes that are already held,
220+
// which should only happen when mutex1 and mutex2 were the same mutex,
221+
// and mutex2 should also be NULL.
222+
if (c->_cs_base._cs_mutex == NULL) {
223+
assert(c->_cs_mutex2 == NULL);
224+
return;
225+
}
202226
if (c->_cs_mutex2) {
203227
PyMutex_Unlock(c->_cs_mutex2);
204228
}

0 commit comments

Comments
 (0)