aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/Objects
diff options
context:
space:
mode:
Diffstat (limited to 'Objects')
-rw-r--r--Objects/codeobject.c2
-rw-r--r--Objects/dictobject.c4
-rw-r--r--Objects/listobject.c3
-rw-r--r--Objects/namespaceobject.c7
-rw-r--r--Objects/object.c76
-rw-r--r--Objects/obmalloc.c96
6 files changed, 123 insertions, 65 deletions
diff --git a/Objects/codeobject.c b/Objects/codeobject.c
index 3f53d4cfeb2..91772bc9d19 100644
--- a/Objects/codeobject.c
+++ b/Objects/codeobject.c
@@ -3369,7 +3369,7 @@ create_tlbc_lock_held(PyCodeObject *co, Py_ssize_t idx)
}
memcpy(new_tlbc->entries, tlbc->entries, tlbc->size * sizeof(void *));
_Py_atomic_store_ptr_release(&co->co_tlbc, new_tlbc);
- _PyMem_FreeDelayed(tlbc);
+ _PyMem_FreeDelayed(tlbc, tlbc->size * sizeof(void *));
tlbc = new_tlbc;
}
char *bc = PyMem_Calloc(1, _PyCode_NBYTES(co));
diff --git a/Objects/dictobject.c b/Objects/dictobject.c
index 72901377cbb..6b7b150f0e2 100644
--- a/Objects/dictobject.c
+++ b/Objects/dictobject.c
@@ -813,7 +813,7 @@ free_keys_object(PyDictKeysObject *keys, bool use_qsbr)
{
#ifdef Py_GIL_DISABLED
if (use_qsbr) {
- _PyMem_FreeDelayed(keys);
+ _PyMem_FreeDelayed(keys, _PyDict_KeysSize(keys));
return;
}
#endif
@@ -858,7 +858,7 @@ free_values(PyDictValues *values, bool use_qsbr)
assert(values->embedded == 0);
#ifdef Py_GIL_DISABLED
if (use_qsbr) {
- _PyMem_FreeDelayed(values);
+ _PyMem_FreeDelayed(values, values_size_from_count(values->capacity));
return;
}
#endif
diff --git a/Objects/listobject.c b/Objects/listobject.c
index c5895645a2d..23d3472b6d4 100644
--- a/Objects/listobject.c
+++ b/Objects/listobject.c
@@ -61,7 +61,8 @@ free_list_items(PyObject** items, bool use_qsbr)
#ifdef Py_GIL_DISABLED
_PyListArray *array = _Py_CONTAINER_OF(items, _PyListArray, ob_item);
if (use_qsbr) {
- _PyMem_FreeDelayed(array);
+ size_t size = sizeof(_PyListArray) + array->allocated * sizeof(PyObject *);
+ _PyMem_FreeDelayed(array, size);
}
else {
PyMem_Free(array);
diff --git a/Objects/namespaceobject.c b/Objects/namespaceobject.c
index caebe6bf543..0fc2bcea4cb 100644
--- a/Objects/namespaceobject.c
+++ b/Objects/namespaceobject.c
@@ -124,9 +124,10 @@ namespace_repr(PyObject *ns)
if (PyUnicode_Check(key) && PyUnicode_GET_LENGTH(key) > 0) {
PyObject *value, *item;
- value = PyDict_GetItemWithError(d, key);
- if (value != NULL) {
+ int has_key = PyDict_GetItemRef(d, key, &value);
+ if (has_key == 1) {
item = PyUnicode_FromFormat("%U=%R", key, value);
+ Py_DECREF(value);
if (item == NULL) {
loop_error = 1;
}
@@ -135,7 +136,7 @@ namespace_repr(PyObject *ns)
Py_DECREF(item);
}
}
- else if (PyErr_Occurred()) {
+ else if (has_key < 0) {
loop_error = 1;
}
}
diff --git a/Objects/object.c b/Objects/object.c
index 6a581a19604..4d60128b092 100644
--- a/Objects/object.c
+++ b/Objects/object.c
@@ -3034,57 +3034,28 @@ finally:
/* Trashcan support. */
-#ifndef Py_GIL_DISABLED
-/* We need to store a pointer in the refcount field of
- * an object. It is important that we never store 0 (NULL).
-* It is also important to not make the object appear immortal,
-* or it might be untracked by the cycle GC. */
-static uintptr_t
-pointer_to_safe_refcount(void *ptr)
-{
- uintptr_t full = (uintptr_t)ptr;
- assert((full & 3) == 0);
-#if SIZEOF_VOID_P > 4
- uint32_t refcnt = (uint32_t)full;
- if (refcnt >= (uint32_t)_Py_IMMORTAL_MINIMUM_REFCNT) {
- full = full - ((uintptr_t)_Py_IMMORTAL_MINIMUM_REFCNT) + 1;
- }
- return full + 2;
-#else
- // Make the top two bits 0, so it appears mortal.
- return (full >> 2) + 1;
-#endif
-}
-
-static void *
-safe_refcount_to_pointer(uintptr_t refcnt)
-{
-#if SIZEOF_VOID_P > 4
- if (refcnt & 1) {
- refcnt += _Py_IMMORTAL_MINIMUM_REFCNT - 1;
- }
- return (void *)(refcnt - 2);
-#else
- return (void *)((refcnt -1) << 2);
-#endif
-}
-#endif
-
/* Add op to the gcstate->trash_delete_later list. Called when the current
- * call-stack depth gets large. op must be a currently untracked gc'ed
- * object, with refcount 0. Py_DECREF must already have been called on it.
+ * call-stack depth gets large. op must be a gc'ed object, with refcount 0.
+ * Py_DECREF must already have been called on it.
*/
void
_PyTrash_thread_deposit_object(PyThreadState *tstate, PyObject *op)
{
_PyObject_ASSERT(op, Py_REFCNT(op) == 0);
+ PyTypeObject *tp = Py_TYPE(op);
+ assert(tp->tp_flags & Py_TPFLAGS_HAVE_GC);
+ int tracked = 0;
+ if (tp->tp_is_gc == NULL || tp->tp_is_gc(op)) {
+ tracked = _PyObject_GC_IS_TRACKED(op);
+ if (tracked) {
+ _PyObject_GC_UNTRACK(op);
+ }
+ }
+ uintptr_t tagged_ptr = ((uintptr_t)tstate->delete_later) | tracked;
#ifdef Py_GIL_DISABLED
- op->ob_tid = (uintptr_t)tstate->delete_later;
+ op->ob_tid = tagged_ptr;
#else
- /* Store the delete_later pointer in the refcnt field. */
- uintptr_t refcnt = pointer_to_safe_refcount(tstate->delete_later);
- *((uintptr_t*)op) = refcnt;
- assert(!_Py_IsImmortal(op));
+ _Py_AS_GC(op)->_gc_next = tagged_ptr;
#endif
tstate->delete_later = op;
}
@@ -3099,17 +3070,17 @@ _PyTrash_thread_destroy_chain(PyThreadState *tstate)
destructor dealloc = Py_TYPE(op)->tp_dealloc;
#ifdef Py_GIL_DISABLED
- tstate->delete_later = (PyObject*) op->ob_tid;
+ uintptr_t tagged_ptr = op->ob_tid;
op->ob_tid = 0;
_Py_atomic_store_ssize_relaxed(&op->ob_ref_shared, _Py_REF_MERGED);
#else
- /* Get the delete_later pointer from the refcnt field.
- * See _PyTrash_thread_deposit_object(). */
- uintptr_t refcnt = *((uintptr_t*)op);
- tstate->delete_later = safe_refcount_to_pointer(refcnt);
- op->ob_refcnt = 0;
+ uintptr_t tagged_ptr = _Py_AS_GC(op)->_gc_next;
+ _Py_AS_GC(op)->_gc_next = 0;
#endif
-
+ tstate->delete_later = (PyObject *)(tagged_ptr & ~1);
+ if (tagged_ptr & 1) {
+ _PyObject_GC_TRACK(op);
+ }
/* Call the deallocator directly. This used to try to
* fool Py_DECREF into calling it indirectly, but
* Py_DECREF was already called on this object, and in
@@ -3183,10 +3154,11 @@ void
_Py_Dealloc(PyObject *op)
{
PyTypeObject *type = Py_TYPE(op);
+ unsigned long gc_flag = type->tp_flags & Py_TPFLAGS_HAVE_GC;
destructor dealloc = type->tp_dealloc;
PyThreadState *tstate = _PyThreadState_GET();
intptr_t margin = _Py_RecursionLimit_GetMargin(tstate);
- if (margin < 2) {
+ if (margin < 2 && gc_flag) {
_PyTrash_thread_deposit_object(tstate, (PyObject *)op);
return;
}
@@ -3232,7 +3204,7 @@ _Py_Dealloc(PyObject *op)
Py_XDECREF(old_exc);
Py_DECREF(type);
#endif
- if (tstate->delete_later && margin >= 4) {
+ if (tstate->delete_later && margin >= 4 && gc_flag) {
_PyTrash_thread_destroy_chain(tstate);
}
}
diff --git a/Objects/obmalloc.c b/Objects/obmalloc.c
index d4b8327cb73..deb7fd957e5 100644
--- a/Objects/obmalloc.c
+++ b/Objects/obmalloc.c
@@ -124,6 +124,33 @@ _PyMem_mi_page_is_safe_to_free(mi_page_t *page)
}
+#ifdef Py_GIL_DISABLED
+
+// If we are deferring collection of more than this amount of memory for
+// mimalloc pages, advance the write sequence. Advancing allows these
+// pages to be re-used in a different thread or for a different size class.
+#define QSBR_PAGE_MEM_LIMIT 4096*20
+
+// Return true if the global write sequence should be advanced for a mimalloc
+// page that is deferred from collection.
+static bool
+should_advance_qsbr_for_page(struct _qsbr_thread_state *qsbr, mi_page_t *page)
+{
+ size_t bsize = mi_page_block_size(page);
+ size_t page_size = page->capacity*bsize;
+ if (page_size > QSBR_PAGE_MEM_LIMIT) {
+ qsbr->deferred_page_memory = 0;
+ return true;
+ }
+ qsbr->deferred_page_memory += page_size;
+ if (qsbr->deferred_page_memory > QSBR_PAGE_MEM_LIMIT) {
+ qsbr->deferred_page_memory = 0;
+ return true;
+ }
+ return false;
+}
+#endif
+
static bool
_PyMem_mi_page_maybe_free(mi_page_t *page, mi_page_queue_t *pq, bool force)
{
@@ -139,7 +166,14 @@ _PyMem_mi_page_maybe_free(mi_page_t *page, mi_page_queue_t *pq, bool force)
_PyMem_mi_page_clear_qsbr(page);
page->retire_expire = 0;
- page->qsbr_goal = _Py_qsbr_deferred_advance(tstate->qsbr);
+
+ if (should_advance_qsbr_for_page(tstate->qsbr, page)) {
+ page->qsbr_goal = _Py_qsbr_advance(tstate->qsbr->shared);
+ }
+ else {
+ page->qsbr_goal = _Py_qsbr_shared_next(tstate->qsbr->shared);
+ }
+
llist_insert_tail(&tstate->mimalloc.page_list, &page->qsbr_node);
return false;
}
@@ -1141,8 +1175,44 @@ free_work_item(uintptr_t ptr, delayed_dealloc_cb cb, void *state)
}
}
+
+#ifdef Py_GIL_DISABLED
+
+// For deferred advance on free: the number of deferred items before advancing
+// the write sequence. This is based on WORK_ITEMS_PER_CHUNK. We ideally
+// want to process a chunk before it overflows.
+#define QSBR_DEFERRED_LIMIT 127
+
+// If the deferred memory exceeds 1 MiB, advance the write sequence. This
+// helps limit memory usage due to QSBR delaying frees too long.
+#define QSBR_FREE_MEM_LIMIT 1024*1024
+
+// Return true if the global write sequence should be advanced for a deferred
+// memory free.
+static bool
+should_advance_qsbr_for_free(struct _qsbr_thread_state *qsbr, size_t size)
+{
+ if (size > QSBR_FREE_MEM_LIMIT) {
+ qsbr->deferred_count = 0;
+ qsbr->deferred_memory = 0;
+ qsbr->should_process = true;
+ return true;
+ }
+ qsbr->deferred_count++;
+ qsbr->deferred_memory += size;
+ if (qsbr->deferred_count > QSBR_DEFERRED_LIMIT ||
+ qsbr->deferred_memory > QSBR_FREE_MEM_LIMIT) {
+ qsbr->deferred_count = 0;
+ qsbr->deferred_memory = 0;
+ qsbr->should_process = true;
+ return true;
+ }
+ return false;
+}
+#endif
+
static void
-free_delayed(uintptr_t ptr)
+free_delayed(uintptr_t ptr, size_t size)
{
#ifndef Py_GIL_DISABLED
free_work_item(ptr, NULL, NULL);
@@ -1200,23 +1270,32 @@ free_delayed(uintptr_t ptr)
}
assert(buf != NULL && buf->wr_idx < WORK_ITEMS_PER_CHUNK);
- uint64_t seq = _Py_qsbr_deferred_advance(tstate->qsbr);
+ uint64_t seq;
+ if (should_advance_qsbr_for_free(tstate->qsbr, size)) {
+ seq = _Py_qsbr_advance(tstate->qsbr->shared);
+ }
+ else {
+ seq = _Py_qsbr_shared_next(tstate->qsbr->shared);
+ }
buf->array[buf->wr_idx].ptr = ptr;
buf->array[buf->wr_idx].qsbr_goal = seq;
buf->wr_idx++;
if (buf->wr_idx == WORK_ITEMS_PER_CHUNK) {
+ // Normally the processing of delayed items is done from the eval
+ // breaker. Processing here is a safety measure to ensure too much
+ // work does not accumulate.
_PyMem_ProcessDelayed((PyThreadState *)tstate);
}
#endif
}
void
-_PyMem_FreeDelayed(void *ptr)
+_PyMem_FreeDelayed(void *ptr, size_t size)
{
assert(!((uintptr_t)ptr & 0x01));
if (ptr != NULL) {
- free_delayed((uintptr_t)ptr);
+ free_delayed((uintptr_t)ptr, size);
}
}
@@ -1226,7 +1305,10 @@ _PyObject_XDecRefDelayed(PyObject *ptr)
{
assert(!((uintptr_t)ptr & 0x01));
if (ptr != NULL) {
- free_delayed(((uintptr_t)ptr)|0x01);
+ // We use 0 as the size since we don't have an easy way to know the
+ // actual size. If we are freeing many objects, the write sequence
+ // will be advanced due to QSBR_DEFERRED_LIMIT.
+ free_delayed(((uintptr_t)ptr)|0x01, 0);
}
}
#endif
@@ -1317,6 +1399,8 @@ _PyMem_ProcessDelayed(PyThreadState *tstate)
PyInterpreterState *interp = tstate->interp;
_PyThreadStateImpl *tstate_impl = (_PyThreadStateImpl *)tstate;
+ tstate_impl->qsbr->should_process = false;
+
// Process thread-local work
process_queue(&tstate_impl->mem_free_queue, tstate_impl, true, NULL, NULL);