Skip to content

Commit 5ac9e6e

Browse files
authored
bpo-33597: Reduce PyGC_Head size (GH-7043)
1 parent 445f1b3 commit 5ac9e6e

File tree

4 files changed

+436
-263
lines changed

4 files changed

+436
-263
lines changed

Include/objimpl.h

Lines changed: 59 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -251,76 +251,88 @@ PyAPI_FUNC(PyVarObject *) _PyObject_GC_Resize(PyVarObject *, Py_ssize_t);
251251

252252
/* GC information is stored BEFORE the object structure. */
253253
#ifndef Py_LIMITED_API
254-
typedef union _gc_head {
255-
struct {
256-
union _gc_head *gc_next;
257-
union _gc_head *gc_prev;
258-
Py_ssize_t gc_refs;
259-
} gc;
260-
double dummy; /* force worst-case alignment */
254+
typedef struct {
255+
// Pointer to next object in the list.
256+
// 0 means the object is not tracked
257+
uintptr_t _gc_next;
258+
259+
// Pointer to previous object in the list.
260+
// Lowest two bits are used for flags documented later.
261+
uintptr_t _gc_prev;
261262
} PyGC_Head;
262263

263264
extern PyGC_Head *_PyGC_generation0;
264265

265266
#define _Py_AS_GC(o) ((PyGC_Head *)(o)-1)
266267

268+
/* Bit flags for _gc_prev */
267269
/* Bit 0 is set when tp_finalize is called */
268-
#define _PyGC_REFS_MASK_FINALIZED (1 << 0)
269-
/* The (N-1) most significant bits contain the gc state / refcount */
270-
#define _PyGC_REFS_SHIFT (1)
271-
#define _PyGC_REFS_MASK (((size_t) -1) << _PyGC_REFS_SHIFT)
272-
273-
#define _PyGCHead_REFS(g) ((g)->gc.gc_refs >> _PyGC_REFS_SHIFT)
274-
#define _PyGCHead_SET_REFS(g, v) do { \
275-
(g)->gc.gc_refs = ((g)->gc.gc_refs & ~_PyGC_REFS_MASK) \
276-
| (((size_t)(v)) << _PyGC_REFS_SHIFT); \
270+
#define _PyGC_PREV_MASK_FINALIZED (1)
271+
/* Bit 1 is set when the object is in generation which is GCed currently. */
272+
#define _PyGC_PREV_MASK_COLLECTING (2)
273+
/* The (N-2) most significant bits contain the real address. */
274+
#define _PyGC_PREV_SHIFT (2)
275+
#define _PyGC_PREV_MASK (((uintptr_t) -1) << _PyGC_PREV_SHIFT)
276+
277+
// Lowest bit of _gc_next is used for flags only in GC.
278+
// But it is always 0 for normal code.
279+
#define _PyGCHead_NEXT(g) ((PyGC_Head*)(g)->_gc_next)
280+
#define _PyGCHead_SET_NEXT(g, p) ((g)->_gc_next = (uintptr_t)(p))
281+
282+
// Lowest two bits of _gc_prev is used for _PyGC_PREV_MASK_* flags.
283+
#define _PyGCHead_PREV(g) ((PyGC_Head*)((g)->_gc_prev & _PyGC_PREV_MASK))
284+
#define _PyGCHead_SET_PREV(g, p) do { \
285+
assert(((uintptr_t)p & ~_PyGC_PREV_MASK) == 0); \
286+
(g)->_gc_prev = ((g)->_gc_prev & ~_PyGC_PREV_MASK) \
287+
| ((uintptr_t)(p)); \
277288
} while (0)
278-
#define _PyGCHead_DECREF(g) ((g)->gc.gc_refs -= 1 << _PyGC_REFS_SHIFT)
279289

280-
#define _PyGCHead_FINALIZED(g) (((g)->gc.gc_refs & _PyGC_REFS_MASK_FINALIZED) != 0)
281-
#define _PyGCHead_SET_FINALIZED(g, v) do { \
282-
(g)->gc.gc_refs = ((g)->gc.gc_refs & ~_PyGC_REFS_MASK_FINALIZED) \
283-
| (v != 0); \
284-
} while (0)
290+
#define _PyGCHead_FINALIZED(g) (((g)->_gc_prev & _PyGC_PREV_MASK_FINALIZED) != 0)
291+
#define _PyGCHead_SET_FINALIZED(g) ((g)->_gc_prev |= _PyGC_PREV_MASK_FINALIZED)
285292

286293
#define _PyGC_FINALIZED(o) _PyGCHead_FINALIZED(_Py_AS_GC(o))
287-
#define _PyGC_SET_FINALIZED(o, v) _PyGCHead_SET_FINALIZED(_Py_AS_GC(o), v)
288-
289-
#define _PyGC_REFS(o) _PyGCHead_REFS(_Py_AS_GC(o))
290-
291-
#define _PyGC_REFS_UNTRACKED (-2)
292-
#define _PyGC_REFS_REACHABLE (-3)
293-
#define _PyGC_REFS_TENTATIVELY_UNREACHABLE (-4)
294-
295-
/* Tell the GC to track this object. NB: While the object is tracked the
296-
* collector it must be safe to call the ob_traverse method. */
294+
#define _PyGC_SET_FINALIZED(o) _PyGCHead_SET_FINALIZED(_Py_AS_GC(o))
295+
296+
/* Tell the GC to track this object.
297+
*
298+
* NB: While the object is tracked by the collector, it must be safe to call the
299+
* ob_traverse method.
300+
*
301+
* Internal note: _PyGC_generation0->_gc_prev doesn't have any bit flags
302+
* because it's not object header. So we don't use _PyGCHead_PREV() and
303+
* _PyGCHead_SET_PREV() for it to avoid unnecessary bitwise operations.
304+
*/
297305
#define _PyObject_GC_TRACK(o) do { \
298306
PyGC_Head *g = _Py_AS_GC(o); \
299-
if (_PyGCHead_REFS(g) != _PyGC_REFS_UNTRACKED) \
307+
if (g->_gc_next != 0) { \
300308
Py_FatalError("GC object already tracked"); \
301-
_PyGCHead_SET_REFS(g, _PyGC_REFS_REACHABLE); \
302-
g->gc.gc_next = _PyGC_generation0; \
303-
g->gc.gc_prev = _PyGC_generation0->gc.gc_prev; \
304-
g->gc.gc_prev->gc.gc_next = g; \
305-
_PyGC_generation0->gc.gc_prev = g; \
309+
} \
310+
assert((g->_gc_prev & _PyGC_PREV_MASK_COLLECTING) == 0); \
311+
PyGC_Head *last = (PyGC_Head*)(_PyGC_generation0->_gc_prev); \
312+
_PyGCHead_SET_NEXT(last, g); \
313+
_PyGCHead_SET_PREV(g, last); \
314+
_PyGCHead_SET_NEXT(g, _PyGC_generation0); \
315+
_PyGC_generation0->_gc_prev = (uintptr_t)g; \
306316
} while (0);
307317

308318
/* Tell the GC to stop tracking this object.
309-
* gc_next doesn't need to be set to NULL, but doing so is a good
310-
* way to provoke memory errors if calling code is confused.
319+
*
320+
* Internal note: This may be called while GC. So _PyGC_PREV_MASK_COLLECTING must
321+
* be cleared. But _PyGC_PREV_MASK_FINALIZED bit is kept.
311322
*/
312323
#define _PyObject_GC_UNTRACK(o) do { \
313324
PyGC_Head *g = _Py_AS_GC(o); \
314-
assert(_PyGCHead_REFS(g) != _PyGC_REFS_UNTRACKED); \
315-
_PyGCHead_SET_REFS(g, _PyGC_REFS_UNTRACKED); \
316-
g->gc.gc_prev->gc.gc_next = g->gc.gc_next; \
317-
g->gc.gc_next->gc.gc_prev = g->gc.gc_prev; \
318-
g->gc.gc_next = NULL; \
325+
PyGC_Head *prev = _PyGCHead_PREV(g); \
326+
PyGC_Head *next = _PyGCHead_NEXT(g); \
327+
assert(next != NULL); \
328+
_PyGCHead_SET_NEXT(prev, next); \
329+
_PyGCHead_SET_PREV(next, prev); \
330+
g->_gc_next = 0; \
331+
g->_gc_prev &= _PyGC_PREV_MASK_FINALIZED; \
319332
} while (0);
320333

321334
/* True if the object is currently tracked by the GC. */
322-
#define _PyObject_GC_IS_TRACKED(o) \
323-
(_PyGC_REFS(o) != _PyGC_REFS_UNTRACKED)
335+
#define _PyObject_GC_IS_TRACKED(o) (_Py_AS_GC(o)->_gc_next != 0)
324336

325337
/* True if the object may be tracked by the GC in the future, or already is.
326338
This can be useful to implement some optimizations. */
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Reduce ``PyGC_Head`` size from 3 words to 2 words.

0 commit comments

Comments
 (0)