8000 gh-114203: Optimise simple recursive critical sections (#128126) · python/cpython@180d417 · GitHub
[go: up one dir, main page]

Skip to content

Commit 180d417

Browse files
authored
gh-114203: Optimise simple recursive critical sections (#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.
1 parent 831b6de commit 180d417

File tree

3 files changed

+32
-7
lines changed

3 files changed

+32
-7
lines changed

Include/internal/pycore_critical_section.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,12 @@ _PyCriticalSection_Pop(PyCriticalSection *c)
145145
static inline void
146146
_PyCriticalSection_End(PyCriticalSection *c)
147147
{
148+
// If the mutex is NULL, we used the fast path in
149+
// _PyCriticalSection_BeginSlow for locks already held in the top-most
150+
// critical section, and we shouldn't unlock or pop this critical section.
151+
if (c->_cs_mutex == NULL) {
152+
return;
153+
}
148154
PyMutex_Unlock(c->_cs_mutex);
149155
_PyCriticalSection_Pop(c);
150156
}
@@ -199,6 +205,14 @@ _PyCriticalSection2_Begin(PyCriticalSection2 *c, PyObject *a, PyObject *b)
199205
static inline void
200206
_PyCriticalSection2_End(PyCriticalSection2 *c)
201207
{
208+
// if mutex1 is NULL, we used the fast path in
209+
// _PyCriticalSection_BeginSlow for mutexes that are already held,
210+
// which should only happen when mutex1 and mutex2 were the same mutex,
211+
// and mutex2 should also be NULL.
212+
if (c->_cs_base._cs_mutex == NULL) {
213+
assert(c->_cs_mutex2 == NULL);
214+
return;
215+
}
202216
if (c->_cs_mutex2) {
203217
PyMutex_Unlock(c->_cs_mutex2);
204218
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Optimize ``Py_BEGIN_CRITICAL_SECTION`` for simple recursive calls.

Python/critical_section.c

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,28 @@ static_assert(_Alignof(PyCriticalSection) >= 4,
88
"critical section must be aligned to at least 4 bytes");
99
#endif
1010

11+
#ifdef Py_GIL_DISABLED
12+
static PyCriticalSection *
13+
untag_critical_section(uintptr_t tag)
14+
{
15+
return (PyCriticalSection *)(tag & ~_Py_CRITICAL_SECTION_MASK);
16+
}
17+
#endif
18+
1119
void
1220
_PyCriticalSection_BeginSlow(PyCriticalSection *c, PyMutex *m)
1321
{
1422
#ifdef Py_GIL_DISABLED
1523
PyThreadState *tstate = _PyThreadState_GET();
24+
// As an optimisation for locking the same object recursively, skip
25+
// locking if the mutex is currently locked by the top-most critical
26+
// section.
27+
if (tstate->critical_section &&
28+
untag_critical_section(tstate->critical_section)->_cs_mutex == m) {
29+
c->_cs_mutex = NULL;
30+
c->_cs_prev = 0;
31+
return;
32+
}
1633
c->_cs_mutex = NULL;
1734
c->_cs_prev = (uintptr_t)tstate->critical_section;
1835
tstate->critical_section = (uintptr_t)c;
@@ -42,13 +59,6 @@ _PyCriticalSection2_BeginSlow(PyCriticalSection2 *c, PyMutex *m1, PyMutex *m2,
4259
#endif
4360
}
4461

45-
#ifdef Py_GIL_DISABLED
46-
static PyCriticalSection *
47-
untag_critical_section(uintptr_t tag)
48-
{
49-
return (PyCriticalSection *)(tag & ~_Py_CRITICAL_SECTION_MASK);
50-
}
51-
#endif
5262

5363
// Release all locks held by critical sections. This is called by
5464
// _PyThreadState_Detach.

0 commit comments

Comments
 (0)
0