Skip to content

Commit 7ef2e30

Browse files
committed
pythongh-133136: Limit excess memory held by QSBR
The free threading build uses QSBR to delay the freeing of dictionary keys and list arrays when the objects are accessed by multiple threads in order to allow concurrent reads to proceeed with holding the object lock. The requests are processed in batches to reduce execution overhead, but for large memory blocks this can lead to excess memory usage. Take into account the size of the memory block when deciding when to process QSBR requests.
1 parent 1ffe913 commit 7ef2e30

File tree

7 files changed

+44
-12
lines changed

7 files changed

+44
-12
lines changed

Include/internal/pycore_pymem.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ extern wchar_t *_PyMem_DefaultRawWcsdup(const wchar_t *str);
8888
extern int _PyMem_DebugEnabled(void);
8989

9090
// Enqueue a pointer to be freed possibly after some delay.
91-
extern void _PyMem_FreeDelayed(void *ptr);
91+
extern void _PyMem_FreeDelayed(void *ptr, size_t size);
9292

9393
// Enqueue an object to be freed possibly after some delay
9494
#ifdef Py_GIL_DISABLED

Include/internal/pycore_qsbr.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ struct _qsbr_thread_state {
5151
// Used to defer advancing write sequence a fixed number of times
5252
int deferrals;
5353

54+
// Estimate for the amount of memory that is held by this thread since
55+
// the last non-deferred advance.
56+
size_t memory_deferred;
57+
5458
// Is this thread state allocated?
5559
bool allocated;
5660
struct _qsbr_thread_state *freelist_next;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Limit excess memory usage in the :term:`free threading` build when a
2+
large dictionary or list is resized and accessed by multiple threads.

Objects/codeobject.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3350,7 +3350,7 @@ create_tlbc_lock_held(PyCodeObject *co, Py_ssize_t idx)
33503350
}
33513351
memcpy(new_tlbc->entries, tlbc->entries, tlbc->size * sizeof(void *));
33523352
_Py_atomic_store_ptr_release(&co->co_tlbc, new_tlbc);
3353-
_PyMem_FreeDelayed(tlbc);
3353+
_PyMem_FreeDelayed(tlbc, tlbc->size * sizeof(void *));
33543354
tlbc = new_tlbc;
33553355
}
33563356
char *bc = PyMem_Calloc(1, _PyCode_NBYTES(co));

Objects/dictobject.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -813,7 +813,7 @@ free_keys_object(PyDictKeysObject *keys, bool use_qsbr)
813813
{
814814
#ifdef Py_GIL_DISABLED
815815
if (use_qsbr) {
816-
_PyMem_FreeDelayed(keys);
816+
_PyMem_FreeDelayed(keys, _PyDict_KeysSize(keys));
817817
return;
818818
}
819819
#endif
@@ -858,7 +858,7 @@ free_values(PyDictValues *values, bool use_qsbr)
858858
assert(values->embedded == 0);
859859
#ifdef Py_GIL_DISABLED
860860
if (use_qsbr) {
861-
_PyMem_FreeDelayed(values);
861+
_PyMem_FreeDelayed(values, values_size_from_count(values->capacity));
862862
return;
863863
}
864864
#endif

Objects/listobject.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ free_list_items(PyObject** items, bool use_qsbr)
6161
#ifdef Py_GIL_DISABLED
6262
_PyListArray *array = _Py_CONTAINER_OF(items, _PyListArray, ob_item);
6363
if (use_qsbr) {
64-
_PyMem_FreeDelayed(array);
64+
size_t size = sizeof(_PyListArray) + array->allocated * sizeof(PyObject *);
65+
_PyMem_FreeDelayed(array, size);
6566
}
6667
else {
6768
PyMem_Free(array);

Objects/obmalloc.c

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1141,8 +1141,27 @@ free_work_item(uintptr_t ptr, delayed_dealloc_cb cb, void *state)
11411141
}
11421142
}
11431143

1144+
static int
1145+
should_advance_qsbr(_PyThreadStateImpl *tstate, size_t size)
1146+
{
1147+
// If the deferred memory exceeds 1 MiB, we force an advance in the
1148+
// shared QSBR sequence number to limit excess memory usage.
1149+
static const size_t QSBR_DEFERRED_LIMIT = 1024 * 1024;
1150+
if (size > QSBR_DEFERRED_LIMIT) {
1151+
tstate->qsbr->memory_deferred = 0;
1152+
return 1;
1153+
}
1154+
1155+
tstate->qsbr->memory_deferred += size;
1156+
if (tstate->qsbr->memory_deferred > QSBR_DEFERRED_LIMIT) {
1157+
tstate->qsbr->memory_deferred = 0;
1158+
return 1;
1159+
}
1160+
return 0;
1161+
}
1162+
11441163
static void
1145-
free_delayed(uintptr_t ptr)
1164+
free_delayed(uintptr_t ptr, size_t size)
11461165
{
11471166
#ifndef Py_GIL_DISABLED
11481167
free_work_item(ptr, NULL, NULL);
@@ -1200,23 +1219,29 @@ free_delayed(uintptr_t ptr)
12001219
}
12011220

12021221
assert(buf != NULL && buf->wr_idx < WORK_ITEMS_PER_CHUNK);
1203-
uint64_t seq = _Py_qsbr_deferred_advance(tstate->qsbr);
1222+
uint64_t seq;
1223+
int force_advance = should_advance_qsbr(tstate, size);
1224+
if (force_advance) {
1225+
seq = _Py_qsbr_advance(tstate->qsbr->shared);
1226+
}
1227+
else {
1228+
seq = _Py_qsbr_deferred_advance(tstate->qsbr);
1229+
}
12041230
buf->array[buf->wr_idx].ptr = ptr;
12051231
buf->array[buf->wr_idx].qsbr_goal = seq;
12061232
buf->wr_idx++;
1207-
1208-
if (buf->wr_idx == WORK_ITEMS_PER_CHUNK) {
1233+
if (buf->wr_idx == WORK_ITEMS_PER_CHUNK || force_advance) {
12091234
_PyMem_ProcessDelayed((PyThreadState *)tstate);
12101235
}
12111236
#endif
12121237
}
12131238

12141239
void
1215-
_PyMem_FreeDelayed(void *ptr)
1240+
_PyMem_FreeDelayed(void *ptr, size_t size)
12161241
{
12171242
assert(!((uintptr_t)ptr & 0x01));
12181243
if (ptr != NULL) {
1219-
free_delayed((uintptr_t)ptr);
1244+
free_delayed((uintptr_t)ptr, size);
12201245
}
12211246
}
12221247

@@ -1226,7 +1251,7 @@ _PyObject_XDecRefDelayed(PyObject *ptr)
12261251
{
12271252
assert(!((uintptr_t)ptr & 0x01));
12281253
if (ptr != NULL) {
1229-
free_delayed(((uintptr_t)ptr)|0x01);
1254+
free_delayed(((uintptr_t)ptr)|0x01, 64);
12301255
}
12311256
}
12321257
#endif

0 commit comments

Comments
 (0)