-
-
Notifications
You must be signed in to change notification settings - Fork 30.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
gh-110481: Implement biased reference counting #110764
Changes from all commits
94bfbde
b1cb396
425d570
8c68c78
86892d9
3c85ca1
c6c6ec6
ac2c957
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -54,16 +54,24 @@ PyAPI_FUNC(int) _PyObject_IsFreed(PyObject *); | |||||||||
Furthermore, we can't use designated initializers in Extensions since these | ||||||||||
are not supported pre-C++20. Thus, keeping an internal copy here is the most | ||||||||||
backwards compatible solution */ | ||||||||||
#if defined(Py_NOGIL) | ||||||||||
#define _PyObject_HEAD_INIT(type) \ | ||||||||||
{ \ | ||||||||||
.ob_ref_local = _Py_IMMORTAL_REFCNT_LOCAL, \ | ||||||||||
.ob_type = (type) \ | ||||||||||
} | ||||||||||
#else | ||||||||||
#define _PyObject_HEAD_INIT(type) \ | ||||||||||
{ \ | ||||||||||
.ob_refcnt = _Py_IMMORTAL_REFCNT, \ | ||||||||||
.ob_type = (type) \ | ||||||||||
}, | ||||||||||
} | ||||||||||
#endif | ||||||||||
#define _PyVarObject_HEAD_INIT(type, size) \ | ||||||||||
{ \ | ||||||||||
.ob_base = _PyObject_HEAD_INIT(type) \ | ||||||||||
.ob_base = _PyObject_HEAD_INIT(type), \ | ||||||||||
.ob_size = size \ | ||||||||||
}, | ||||||||||
} | ||||||||||
corona10 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
|
||||||||||
extern void _Py_NO_RETURN _Py_FatalRefcountErrorFunc( | ||||||||||
const char *func, | ||||||||||
|
@@ -95,24 +103,63 @@ static inline void _Py_RefcntAdd(PyObject* op, Py_ssize_t n) | |||||||||
#ifdef Py_REF_DEBUG | ||||||||||
_Py_AddRefTotal(_PyInterpreterState_GET(), n); | ||||||||||
#endif | ||||||||||
#if !defined(Py_NOGIL) | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this worth the extra code? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you suggesting removing |
||||||||||
op->ob_refcnt += n; | ||||||||||
#else | ||||||||||
if (_Py_IsOwnedByCurrentThread(op)) { | ||||||||||
uint32_t local = op->ob_ref_local; | ||||||||||
Py_ssize_t refcnt = (Py_ssize_t)local + n; | ||||||||||
# if PY_SSIZE_T_MAX > UINT32_MAX | ||||||||||
if (refcnt > (Py_ssize_t)UINT32_MAX) { | ||||||||||
// Make the object immortal if the 32-bit local reference count | ||||||||||
// would overflow. | ||||||||||
refcnt = _Py_IMMORTAL_REFCNT_LOCAL; | ||||||||||
} | ||||||||||
# endif | ||||||||||
_Py_atomic_store_uint32_relaxed(&op->ob_ref_local, (uint32_t)refcnt); | ||||||||||
} | ||||||||||
else { | ||||||||||
_Py_atomic_add_ssize(&op->ob_ref_shared, (n << _Py_REF_SHARED_SHIFT)); | ||||||||||
} | ||||||||||
#endif | ||||||||||
} | ||||||||||
#define _Py_RefcntAdd(op, n) _Py_RefcntAdd(_PyObject_CAST(op), n) | ||||||||||
|
||||||||||
static inline void _Py_SetImmortal(PyObject *op) | ||||||||||
{ | ||||||||||
if (op) { | ||||||||||
#ifdef Py_NOGIL | ||||||||||
op->ob_tid = _Py_UNOWNED_TID; | ||||||||||
op->ob_ref_local = _Py_IMMORTAL_REFCNT_LOCAL; | ||||||||||
op->ob_ref_shared = 0; | ||||||||||
#else | ||||||||||
op->ob_refcnt = _Py_IMMORTAL_REFCNT; | ||||||||||
#endif | ||||||||||
} | ||||||||||
} | ||||||||||
#define _Py_SetImmortal(op) _Py_SetImmortal(_PyObject_CAST(op)) | ||||||||||
|
||||||||||
// Makes an immortal object mortal again with the specified refcnt. Should only | ||||||||||
// be used during runtime finalization. | ||||||||||
static inline void _Py_SetMortal(PyObject *op, Py_ssize_t refcnt) | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function shouldn't exist. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is important for the reference leak checks. It's not new functionality. Without it, we would leak immortal objects on interpreter shutdown. It's not generally safe, which is why it's in an We use this in two places. In both our intention is to deallocate immortal objects.
|
||||||||||
{ | ||||||||||
if (op) { | ||||||||||
assert(_Py_IsImmortal(op)); | ||||||||||
#ifdef Py_NOGIL | ||||||||||
op->ob_tid = _Py_UNOWNED_TID; | ||||||||||
op->ob_ref_local = 0; | ||||||||||
op->ob_ref_shared = _Py_REF_SHARED(refcnt, _Py_REF_MERGED); | ||||||||||
#else | ||||||||||
op->ob_refcnt = refcnt; | ||||||||||
#endif | ||||||||||
} | ||||||||||
} | ||||||||||
|
||||||||||
/* _Py_ClearImmortal() should only be used during runtime finalization. */ | ||||||||||
static inline void _Py_ClearImmortal(PyObject *op) | ||||||||||
{ | ||||||||||
if (op) { | ||||||||||
assert(op->ob_refcnt == _Py_IMMORTAL_REFCNT); | ||||||||||
op->ob_refcnt = 1; | ||||||||||
_Py_SetMortal(op, 1); | ||||||||||
Py_DECREF(op); | ||||||||||
} | ||||||||||
} | ||||||||||
|
@@ -122,6 +169,7 @@ static inline void _Py_ClearImmortal(PyObject *op) | |||||||||
op = NULL; \ | ||||||||||
} while (0) | ||||||||||
|
||||||||||
#if !defined(Py_NOGIL) | ||||||||||
static inline void | ||||||||||
_Py_DECREF_SPECIALIZED(PyObject *op, const destructor destruct) | ||||||||||
{ | ||||||||||
|
@@ -161,6 +209,37 @@ _Py_DECREF_NO_DEALLOC(PyObject *op) | |||||||||
#endif | ||||||||||
} | ||||||||||
|
||||||||||
#else | ||||||||||
// TODO: implement Py_DECREF specializations for Py_NOGIL build | ||||||||||
static inline void | ||||||||||
_Py_DECREF_SPECIALIZED(PyObject *op, const destructor destruct) | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not keep the specialization? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm trying to keep the PR small so that it's easier to review. I intend to implement the specializations later. |
||||||||||
{ | ||||||||||
Py_DECREF(op); | ||||||||||
} | ||||||||||
|
||||||||||
static inline void | ||||||||||
_Py_DECREF_NO_DEALLOC(PyObject *op) | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Given we know that the refcount cannot reach zero, why not take advantage of that? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I intend to implement this later. |
||||||||||
{ | ||||||||||
Py_DECREF(op); | ||||||||||
} | ||||||||||
|
||||||||||
static inline int | ||||||||||
_Py_REF_IS_MERGED(Py_ssize_t ob_ref_shared) | ||||||||||
{ | ||||||||||
return (ob_ref_shared & _Py_REF_SHARED_FLAG_MASK) == _Py_REF_MERGED; | ||||||||||
} | ||||||||||
|
||||||||||
static inline int | ||||||||||
_Py_REF_IS_QUEUED(Py_ssize_t ob_ref_shared) | ||||||||||
{ | ||||||||||
return (ob_ref_shared & _Py_REF_SHARED_FLAG_MASK) == _Py_REF_QUEUED; | ||||||||||
} | ||||||||||
|
||||||||||
// Merge the local and shared reference count fields and add `extra` to the | ||||||||||
// refcount when merging. | ||||||||||
Py_ssize_t _Py_ExplicitMergeRefcount(PyObject *op, Py_ssize_t extra); | ||||||||||
#endif // !defined(Py_NOGIL) | ||||||||||
|
||||||||||
#ifdef Py_REF_DEBUG | ||||||||||
# undef _Py_DEC_REFTOTAL | ||||||||||
#endif | ||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Loosing the trailing comma is definitely better style, but is bound to break some third party code.
I think you need to leave the comma.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is in an internal-only header (
Include/internal/pycore_object.h
), not used by third party code.There's a similarly named public macro used by third party code that is not changed.