mirror of
https://github.com/python/cpython
synced 2026-09-29 09:25:29 +03:00
gh-110481: Implement biased reference counting (gh-110764)
This commit is contained in:
parent
05f2f0ac92
commit
6dfb8fe023
29 changed files with 511 additions and 52 deletions
137
Objects/object.c
137
Objects/object.c
|
|
@ -296,6 +296,124 @@ _Py_DecRef(PyObject *o)
|
|||
Py_DECREF(o);
|
||||
}
|
||||
|
||||
#ifdef Py_NOGIL
|
||||
static inline int
|
||||
is_shared_refcnt_dead(Py_ssize_t shared)
|
||||
{
|
||||
#if SIZEOF_SIZE_T == 8
|
||||
return shared == (Py_ssize_t)0xDDDDDDDDDDDDDDDD;
|
||||
#else
|
||||
return shared == (Py_ssize_t)0xDDDDDDDD;
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
_Py_DecRefSharedDebug(PyObject *o, const char *filename, int lineno)
|
||||
{
|
||||
// Should we queue the object for the owning thread to merge?
|
||||
int should_queue;
|
||||
|
||||
Py_ssize_t new_shared;
|
||||
Py_ssize_t shared = _Py_atomic_load_ssize_relaxed(&o->ob_ref_shared);
|
||||
do {
|
||||
should_queue = (shared == 0 || shared == _Py_REF_MAYBE_WEAKREF);
|
||||
|
||||
if (should_queue) {
|
||||
// If the object had refcount zero, not queued, and not merged,
|
||||
// then we enqueue the object to be merged by the owning thread.
|
||||
// In this case, we don't subtract one from the reference count
|
||||
// because the queue holds a reference.
|
||||
new_shared = _Py_REF_QUEUED;
|
||||
}
|
||||
else {
|
||||
// Otherwise, subtract one from the reference count. This might
|
||||
// be negative!
|
||||
new_shared = shared - (1 << _Py_REF_SHARED_SHIFT);
|
||||
}
|
||||
|
||||
#ifdef Py_REF_DEBUG
|
||||
if ((_Py_REF_IS_MERGED(new_shared) && new_shared < 0) ||
|
||||
is_shared_refcnt_dead(shared))
|
||||
{
|
||||
_Py_NegativeRefcount(filename, lineno, o);
|
||||
}
|
||||
#endif
|
||||
} while (!_Py_atomic_compare_exchange_ssize(&o->ob_ref_shared,
|
||||
&shared, new_shared));
|
||||
|
||||
if (should_queue) {
|
||||
// TODO: the inter-thread queue is not yet implemented. For now,
|
||||
// we just merge the refcount here.
|
||||
Py_ssize_t refcount = _Py_ExplicitMergeRefcount(o, -1);
|
||||
if (refcount == 0) {
|
||||
_Py_Dealloc(o);
|
||||
}
|
||||
}
|
||||
else if (new_shared == _Py_REF_MERGED) {
|
||||
// refcount is zero AND merged
|
||||
_Py_Dealloc(o);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
_Py_DecRefShared(PyObject *o)
|
||||
{
|
||||
_Py_DecRefSharedDebug(o, NULL, 0);
|
||||
}
|
||||
|
||||
void
|
||||
_Py_MergeZeroLocalRefcount(PyObject *op)
|
||||
{
|
||||
assert(op->ob_ref_local == 0);
|
||||
|
||||
_Py_atomic_store_uintptr_relaxed(&op->ob_tid, 0);
|
||||
Py_ssize_t shared = _Py_atomic_load_ssize_relaxed(&op->ob_ref_shared);
|
||||
if (shared == 0) {
|
||||
// Fast-path: shared refcount is zero (including flags)
|
||||
_Py_Dealloc(op);
|
||||
return;
|
||||
}
|
||||
|
||||
// Slow-path: atomically set the flags (low two bits) to _Py_REF_MERGED.
|
||||
Py_ssize_t new_shared;
|
||||
do {
|
||||
new_shared = (shared & ~_Py_REF_SHARED_FLAG_MASK) | _Py_REF_MERGED;
|
||||
} while (!_Py_atomic_compare_exchange_ssize(&op->ob_ref_shared,
|
||||
&shared, new_shared));
|
||||
|
||||
if (new_shared == _Py_REF_MERGED) {
|
||||
// i.e., the shared refcount is zero (only the flags are set) so we
|
||||
// deallocate the object.
|
||||
_Py_Dealloc(op);
|
||||
}
|
||||
}
|
||||
|
||||
Py_ssize_t
|
||||
_Py_ExplicitMergeRefcount(PyObject *op, Py_ssize_t extra)
|
||||
{
|
||||
assert(!_Py_IsImmortal(op));
|
||||
Py_ssize_t refcnt;
|
||||
Py_ssize_t new_shared;
|
||||
Py_ssize_t shared = _Py_atomic_load_ssize_relaxed(&op->ob_ref_shared);
|
||||
do {
|
||||
refcnt = Py_ARITHMETIC_RIGHT_SHIFT(Py_ssize_t, shared, _Py_REF_SHARED_SHIFT);
|
||||
if (_Py_REF_IS_MERGED(shared)) {
|
||||
return refcnt;
|
||||
}
|
||||
|
||||
refcnt += (Py_ssize_t)op->ob_ref_local;
|
||||
refcnt += extra;
|
||||
|
||||
new_shared = _Py_REF_SHARED(refcnt, _Py_REF_MERGED);
|
||||
} while (!_Py_atomic_compare_exchange_ssize(&op->ob_ref_shared,
|
||||
&shared, new_shared));
|
||||
|
||||
_Py_atomic_store_uint32_relaxed(&op->ob_ref_local, 0);
|
||||
_Py_atomic_store_uintptr_relaxed(&op->ob_tid, 0);
|
||||
return refcnt;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/**************************************/
|
||||
|
||||
|
|
@ -1926,10 +2044,7 @@ PyTypeObject _PyNone_Type = {
|
|||
none_new, /*tp_new */
|
||||
};
|
||||
|
||||
PyObject _Py_NoneStruct = {
|
||||
{ _Py_IMMORTAL_REFCNT },
|
||||
&_PyNone_Type
|
||||
};
|
||||
PyObject _Py_NoneStruct = _PyObject_HEAD_INIT(&_PyNone_Type);
|
||||
|
||||
/* NotImplemented is an object that can be used to signal that an
|
||||
operation is not implemented for the given type combination. */
|
||||
|
|
@ -2028,10 +2143,7 @@ PyTypeObject _PyNotImplemented_Type = {
|
|||
notimplemented_new, /*tp_new */
|
||||
};
|
||||
|
||||
PyObject _Py_NotImplementedStruct = {
|
||||
{ _Py_IMMORTAL_REFCNT },
|
||||
&_PyNotImplemented_Type
|
||||
};
|
||||
PyObject _Py_NotImplementedStruct = _PyObject_HEAD_INIT(&_PyNotImplemented_Type);
|
||||
|
||||
|
||||
PyStatus
|
||||
|
|
@ -2248,7 +2360,16 @@ new_reference(PyObject *op)
|
|||
_PyTraceMalloc_NewReference(op);
|
||||
}
|
||||
// Skip the immortal object check in Py_SET_REFCNT; always set refcnt to 1
|
||||
#if !defined(Py_NOGIL)
|
||||
op->ob_refcnt = 1;
|
||||
#else
|
||||
op->ob_tid = _Py_ThreadId();
|
||||
op->_padding = 0;
|
||||
op->ob_mutex = 0;
|
||||
op->ob_gc_bits = 0;
|
||||
op->ob_ref_local = 1;
|
||||
op->ob_ref_shared = 0;
|
||||
#endif
|
||||
#ifdef Py_TRACE_REFS
|
||||
_Py_AddToAllObjects(op);
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -2394,7 +2394,4 @@ static PyTypeObject _PySetDummy_Type = {
|
|||
Py_TPFLAGS_DEFAULT, /*tp_flags */
|
||||
};
|
||||
|
||||
static PyObject _dummy_struct = {
|
||||
{ _Py_IMMORTAL_REFCNT },
|
||||
&_PySetDummy_Type
|
||||
};
|
||||
static PyObject _dummy_struct = _PyObject_HEAD_INIT(&_PySetDummy_Type);
|
||||
|
|
|
|||
|
|
@ -98,10 +98,7 @@ PyTypeObject PyEllipsis_Type = {
|
|||
ellipsis_new, /* tp_new */
|
||||
};
|
||||
|
||||
PyObject _Py_EllipsisObject = {
|
||||
{ _Py_IMMORTAL_REFCNT },
|
||||
&PyEllipsis_Type
|
||||
};
|
||||
PyObject _Py_EllipsisObject = _PyObject_HEAD_INIT(&PyEllipsis_Type);
|
||||
|
||||
|
||||
/* Slice object implementation */
|
||||
|
|
|
|||
|
|
@ -14967,7 +14967,7 @@ _PyUnicode_ClearInterned(PyInterpreterState *interp)
|
|||
// Skip the Immortal Instance check and restore
|
||||
// the two references (key and value) ignored
|
||||
// by PyUnicode_InternInPlace().
|
||||
s->ob_refcnt = 2;
|
||||
_Py_SetMortal(s, 2);
|
||||
#ifdef INTERNED_STATS
|
||||
total_length += PyUnicode_GET_LENGTH(s);
|
||||
#endif
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue