Skip to content
This repository was archived by the owner on May 20, 2025. It is now read-only.

Commit b6b12a9

Browse files
committed
Implement biased reference counting
1 parent cfb6ed1 commit b6b12a9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+1153
-221
lines changed

Include/boolobject.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ PyAPI_FUNC(int) Py_IsFalse(PyObject *x);
3131
#define Py_IsFalse(x) Py_Is((x), Py_False)
3232

3333
/* Macros for returning Py_True or Py_False, respectively */
34-
#define Py_RETURN_TRUE return Py_NewRef(Py_True)
35-
#define Py_RETURN_FALSE return Py_NewRef(Py_False)
34+
#define Py_RETURN_TRUE return Py_True
35+
#define Py_RETURN_FALSE return Py_False
3636

3737
/* Function to return a bool from a C long */
3838
PyAPI_FUNC(PyObject *) PyBool_FromLong(long);

Include/cpython/object.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#endif
44

55
PyAPI_FUNC(void) _Py_NewReference(PyObject *op);
6+
PyAPI_FUNC(void) _Py_ReattachReference(PyObject *op);
67

78
#ifdef Py_TRACE_REFS
89
/* Py_TRACE_REFS is such major surgery that we call external routines. */

Include/cpython/pystate.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,8 @@ struct _ts {
215215
PyObject *context;
216216
uint64_t context_ver;
217217

218+
Py_ssize_t ref_total;
219+
218220
/* Unique thread state id. */
219221
uint64_t id;
220222

Include/cpython/weakrefobject.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ static inline PyObject* PyWeakref_GET_OBJECT(PyObject *ref_obj) {
4848
// has dropped to zero. In the meantime, code accessing the weakref will
4949
// be able to "see" the target object even though it is supposed to be
5050
// unreachable. See issue gh-60806.
51-
if (Py_REFCNT(obj) > 0) {
51+
if (Py_IS_REFERENCED(obj)) {
5252
return obj;
5353
}
5454
return Py_None;

Include/internal/pycore_global_objects_fini_generated.h

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_interp.h

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ extern "C" {
2222
#include "pycore_genobject.h" // struct _Py_async_gen_state
2323
#include "pycore_gc.h" // struct _gc_runtime_state
2424
#include "pycore_list.h" // struct _Py_list_state
25+
#include "pycore_llist.h" // struct llist_node
2526
#include "pycore_global_objects.h" // struct _Py_interp_static_objects
2627
#include "pycore_tuple.h" // struct _Py_tuple_state
2728
#include "pycore_typeobject.h" // struct type_cache
@@ -47,6 +48,28 @@ struct _Py_long_state {
4748
int max_str_digits;
4849
};
4950

51+
/* Defined in pycore_refcnt.h */
52+
typedef struct _PyObjectQueue _PyObjectQueue;
53+
54+
/* Biased reference counting per-thread state */
55+
struct brc_state {
56+
/* linked-list of thread states per hash bucket */
57+
struct llist_node bucket_node;
58+
59+
/* queue of objects to be merged (protected by bucket mutex) */
60+
_PyObjectQueue *queue;
61+
62+
/* local queue of objects to be merged */
63+
_PyObjectQueue *local_queue;
64+
};
65+
66+
typedef struct PyThreadStateImpl {
67+
// semi-public fields are in PyThreadState
68+
PyThreadState tstate;
69+
70+
struct brc_state brc;
71+
} PyThreadStateImpl;
72+
5073

5174
/* interpreter state */
5275

@@ -199,7 +222,7 @@ struct _is {
199222
*/
200223

201224
/* the initial PyInterpreterState.threads.head */
202-
PyThreadState _initial_thread;
225+
PyThreadStateImpl _initial_thread;
203226
};
204227

205228

Include/internal/pycore_long.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ static inline PyObject* _PyLong_GetOne(void)
7575

7676
static inline PyObject* _PyLong_FromUnsignedChar(unsigned char i)
7777
{
78-
return Py_NewRef((PyObject *)&_PyLong_SMALL_INTS[_PY_NSMALLNEGINTS+i]);
78+
return (PyObject *)&_PyLong_SMALL_INTS[_PY_NSMALLNEGINTS+i];
7979
}
8080

8181
PyObject *_PyLong_Add(PyLongObject *left, PyLongObject *right);

Include/internal/pycore_object.h

Lines changed: 188 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ extern "C" {
2121

2222
#define _PyObject_IMMORTAL_INIT(type) \
2323
{ \
24-
.ob_refcnt = _PyObject_IMMORTAL_REFCNT, \
25-
.ob_type = (type), \
24+
.ob_tid = (uintptr_t)Py_REF_IMMORTAL, \
25+
.ob_ref_local = (uint32_t)Py_REF_IMMORTAL, \
26+
.ob_type = type, \
2627
}
2728
#define _PyVarObject_IMMORTAL_INIT(type, size) \
2829
{ \
@@ -40,48 +41,39 @@ PyAPI_FUNC(void) _Py_NO_RETURN _Py_FatalRefcountErrorFunc(
4041
// Increment reference count by n
4142
static inline void _Py_RefcntAdd(PyObject* op, Py_ssize_t n)
4243
{
44+
uint32_t local = _Py_atomic_load_uint32_relaxed(&op->ob_ref_local);
45+
if (_Py_REF_IS_IMMORTAL(local)) {
46+
return;
47+
}
48+
4349
#ifdef Py_REF_DEBUG
44-
_Py_RefTotal += n;
50+
_Py_IncRefTotalN(n);
4551
#endif
46-
op->ob_refcnt += n;
52+
if (_PY_LIKELY(_Py_ThreadLocal(op))) {
53+
local += _Py_STATIC_CAST(uint32_t, (n << _Py_REF_LOCAL_SHIFT));
54+
_Py_atomic_store_uint32_relaxed(&op->ob_ref_local, local);
55+
}
56+
else {
57+
_Py_atomic_add_uint32(&op->ob_ref_shared, _Py_STATIC_CAST(uint32_t, n << _Py_REF_SHARED_SHIFT));
58+
}
4759
}
4860
#define _Py_RefcntAdd(op, n) _Py_RefcntAdd(_PyObject_CAST(op), n)
4961

50-
static inline void
62+
static _Py_ALWAYS_INLINE void
5163
_Py_DECREF_SPECIALIZED(PyObject *op, const destructor destruct)
5264
{
53-
_Py_DECREF_STAT_INC();
54-
#ifdef Py_REF_DEBUG
55-
_Py_RefTotal--;
56-
#endif
57-
if (--op->ob_refcnt != 0) {
58-
assert(op->ob_refcnt > 0);
59-
}
60-
else {
61-
#ifdef Py_TRACE_REFS
62-
_Py_ForgetReference(op);
63-
#endif
64-
destruct(op);
65-
}
65+
Py_DECREF(op);
6666
}
6767

68-
static inline void
68+
static _Py_ALWAYS_INLINE void
6969
_Py_DECREF_NO_DEALLOC(PyObject *op)
7070
{
71-
_Py_DECREF_STAT_INC();
72-
#ifdef Py_REF_DEBUG
73-
_Py_RefTotal--;
74-
#endif
75-
op->ob_refcnt--;
76-
#ifdef Py_DEBUG
77-
if (op->ob_refcnt <= 0) {
78-
_Py_FatalRefcountError("Expected a positive remaining refcount");
79-
}
80-
#endif
71+
Py_DECREF(op);
8172
}
8273

8374
PyAPI_FUNC(int) _PyType_CheckConsistency(PyTypeObject *type);
8475
PyAPI_FUNC(int) _PyDict_CheckConsistency(PyObject *mp, int check_content);
76+
PyAPI_FUNC(void) _PyObject_Dealloc(PyObject *self);
8577

8678
/* Update the Python traceback of an object. This function must be called
8779
when a memory block is reused from a free list.
@@ -207,6 +199,173 @@ static inline void _PyObject_GC_UNTRACK(
207199
_PyObject_GC_UNTRACK(__FILE__, __LINE__, _PyObject_CAST(op))
208200
#endif
209201

202+
/* Tries to increment an object's reference count
203+
*
204+
* This is a specialized version of _Py_TryIncref that only succeeds if the
205+
* object is immortal or local to this thread. It does not handle the case
206+
* where the reference count modification requires an atomic operation. This
207+
* allows call sites to specialize for the immortal/local case.
208+
*/
209+
Py_ALWAYS_INLINE static inline int
210+
_Py_TryIncrefFast(PyObject *op) {
211+
uint32_t local = _Py_atomic_load_uint32_relaxed(&op->ob_ref_local);
212+
local += (1 << _Py_REF_LOCAL_SHIFT);
213+
if (local == 0) {
214+
// immortal
215+
return 1;
216+
}
217+
if (_PY_LIKELY(_Py_ThreadLocal(op))) {
218+
_Py_atomic_store_uint32_relaxed(&op->ob_ref_local, local);
219+
#ifdef Py_REF_DEBUG
220+
_Py_IncRefTotal();
221+
#endif
222+
return 1;
223+
}
224+
return 0;
225+
}
226+
227+
static _Py_ALWAYS_INLINE int
228+
_Py_TryIncRefShared(PyObject *op)
229+
{
230+
for (;;) {
231+
uint32_t shared = _Py_atomic_load_uint32_relaxed(&op->ob_ref_shared);
232+
233+
// If the shared refcount is zero and the object is either merged
234+
// or may not have weak references, then we cannot incref it.
235+
if (shared == 0 || shared == _Py_REF_MERGED) {
236+
return 0;
237+
}
238+
239+
if (_Py_atomic_compare_exchange_uint32(
240+
&op->ob_ref_shared,
241+
shared,
242+
shared + (1 << _Py_REF_SHARED_SHIFT))) {
243+
#ifdef Py_REF_DEBUG
244+
_Py_IncRefTotal();
245+
#endif
246+
return 1;
247+
}
248+
}
249+
}
250+
251+
/* Tries to incref the object op and ensures that *src still points to it. */
252+
static inline int
253+
_Py_TryAcquireObject(PyObject **src, PyObject *op)
254+
{
255+
if (_Py_TryIncrefFast(op)) {
256+
return 1;
257+
}
258+
if (!_Py_TryIncRefShared(op)) {
259+
return 0;
260+
}
261+
if (op != _Py_atomic_load_ptr(src)) {
262+
Py_DECREF(op);
263+
return 0;
264+
}
265+
return 1;
266+
}
267+
268+
/* Loads and increfs an object from ptr, which may contain a NULL value.
269+
Safe with concurrent (atomic) updates to ptr.
270+
NOTE: The writer must set maybe-weakref on the stored object! */
271+
static _Py_ALWAYS_INLINE PyObject *
272+
_Py_XFetchRef(PyObject **ptr)
273+
{
274+
#ifdef Py_NOGIL
275+
for (;;) {
276+
PyObject *value = _Py_atomic_load_ptr(ptr);
277+
if (value == NULL) {
278+
return value;
279+
}
280+
if (_Py_TryAcquireObject(ptr, value)) {
281+
return value;
282+
}
283+
}
284+
#else
285+
return Py_XNewRef(*ptr);
286+
#endif
287+
}
288+
289+
/* Attempts to loads and increfs an object from ptr. Returns NULL
290+
on failure, which may be due to a NULL value or a concurrent update. */
291+
static _Py_ALWAYS_INLINE PyObject *
292+
_Py_TryXFetchRef(PyObject **ptr)
293+
{
294+
PyObject *value = _Py_atomic_load_ptr(ptr);
295+
if (value == NULL) {
296+
return value;
297+
}
298+
if (_Py_TryAcquireObject(ptr, value)) {
299+
return value;
300+
}
301+
return NULL;
302+
}
303+
304+
/* Like Py_NewRef but also optimistically sets _Py_REF_MAYBE_WEAKREF
305+
on objects owned by a different thread. */
306+
static inline PyObject *
307+
_Py_NewRefWithLock(PyObject *op)
308+
{
309+
_Py_INCREF_STAT_INC();
310+
uint32_t local = _Py_atomic_load_uint32_relaxed(&op->ob_ref_local);
311+
local += (1 << _Py_REF_LOCAL_SHIFT);
312+
if (local == 0) {
313+
return op;
314+
}
315+
316+
#ifdef Py_REF_DEBUG
317+
_Py_IncRefTotal();
318+
#endif
319+
if (_Py_ThreadLocal(op)) {
320+
_Py_atomic_store_uint32_relaxed(&op->ob_ref_local, local);
321+
}
322+
else {
323+
for (;;) {
324+
uint32_t shared = _Py_atomic_load_uint32_relaxed(&op->ob_ref_shared);
325+
uint32_t new_shared = shared + (1 << _Py_REF_SHARED_SHIFT);
326+
if ((shared & _Py_REF_SHARED_FLAG_MASK) == 0) {
327+
new_shared |= _Py_REF_MAYBE_WEAKREF;
328+
}
329+
if (_Py_atomic_compare_exchange_uint32(
330+
&op->ob_ref_shared,
331+
shared,
332+
new_shared)) {
333+
return op;
334+
}
335+
}
336+
}
337+
return op;
338+
}
339+
340+
static inline PyObject *
341+
_Py_XNewRefWithLock(PyObject *obj)
342+
{
343+
if (obj == NULL) {
344+
return NULL;
345+
}
346+
return _Py_NewRefWithLock(obj);
347+
}
348+
349+
static inline void
350+
_PyObject_SetMaybeWeakref(PyObject *op)
351+
{
352+
if (_PyObject_IS_IMMORTAL(op)) {
353+
return;
354+
}
355+
for (;;) {
356+
uint32_t shared = _Py_atomic_load_uint32_relaxed(&op->ob_ref_shared);
357+
if ((shared & _Py_REF_SHARED_FLAG_MASK) != 0) {
358+
return;
359+
}
360+
if (_Py_atomic_compare_exchange_uint32(
361+
&op->ob_ref_shared,
362+
shared,
363+
shared | _Py_REF_MAYBE_WEAKREF)) {
364+
return;
365+
}
366+
}
367+
}
368+
210369
#ifdef Py_REF_DEBUG
211370
extern void _PyDebug_PrintTotalRefs(void);
212371
#endif

Include/internal/pycore_pystate.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ extern "C" {
88
# error "this header requires Py_BUILD_CORE define"
99
#endif
1010

11+
#include "pycore_llist.h" /* llist_data */
1112
#include "pycore_runtime.h" /* PyRuntimeState */
1213

1314
enum _threadstatus {
@@ -151,6 +152,8 @@ static inline PyInterpreterState* _PyInterpreterState_GET(void) {
151152

152153
PyAPI_FUNC(void) _PyThreadState_SetCurrent(PyThreadState *tstate);
153154
// We keep this around exclusively for stable ABI compatibility.
155+
/* Other */
156+
154157
PyAPI_FUNC(void) _PyThreadState_Init(
155158
PyThreadState *tstate);
156159
PyAPI_FUNC(void) _PyThreadState_DeleteExcept(

0 commit comments

Comments
 (0)