Skip to content

Commit 2923163

Browse files
committed
Revert "pythonGH-126491: GC: Mark objects reachable from roots before doing cycle collection (pythonGH-127110)"
This reverts commit a8dd821.
1 parent 8ba9f5b commit 2923163

File tree

14 files changed

+103
-355
lines changed

14 files changed

+103
-355
lines changed

Include/cpython/pystats.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,6 @@ typedef struct _gc_stats {
9999
uint64_t collections;
100100
uint64_t object_visits;
101101
uint64_t objects_collected;
102-
uint64_t objects_transitively_reachable;
103-
uint64_t objects_not_transitively_reachable;
104102
} GCStats;
105103

106104
typedef struct _uop_stats {

Include/internal/pycore_frame.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ typedef struct _PyInterpreterFrame {
7575
_PyStackRef *stackpointer;
7676
uint16_t return_offset; /* Only relevant during a function call */
7777
char owner;
78-
char visited;
7978
/* Locals and stack */
8079
_PyStackRef localsplus[1];
8180
} _PyInterpreterFrame;
@@ -208,7 +207,6 @@ _PyFrame_Initialize(
208207
#endif
209208
frame->return_offset = 0;
210209
frame->owner = FRAME_OWNED_BY_THREAD;
211-
frame->visited = 0;
212210

213211
for (int i = null_locals_from; i < code->co_nlocalsplus; i++) {
214212
frame->localsplus[i] = PyStackRef_NULL;
@@ -391,7 +389,6 @@ _PyFrame_PushTrampolineUnchecked(PyThreadState *tstate, PyCodeObject *code, int
391389
frame->instr_ptr = _PyCode_CODE(code);
392390
#endif
393391
frame->owner = FRAME_OWNED_BY_THREAD;
394-
frame->visited = 0;
395392
frame->return_offset = 0;
396393

397394
#ifdef Py_GIL_DISABLED

Include/internal/pycore_gc.h

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ extern "C" {
1010

1111
/* GC information is stored BEFORE the object structure. */
1212
typedef struct {
13-
// Tagged pointer to next object in the list.
13+
// Pointer to next object in the list.
1414
// 0 means the object is not tracked
1515
uintptr_t _gc_next;
1616

17-
// Tagged pointer to previous object in the list.
17+
// Pointer to previous object in the list.
1818
// Lowest two bits are used for flags documented later.
1919
uintptr_t _gc_prev;
2020
} PyGC_Head;
@@ -284,11 +284,6 @@ struct gc_generation_stats {
284284
Py_ssize_t uncollectable;
285285
};
286286

287-
enum _GCPhase {
288-
GC_PHASE_MARK = 0,
289-
GC_PHASE_COLLECT = 1
290-
};
291-
292287
struct _gc_runtime_state {
293288
/* List of objects that still need to be cleaned up, singly linked
294289
* via their gc headers' gc_prev pointers. */
@@ -316,7 +311,6 @@ struct _gc_runtime_state {
316311
Py_ssize_t work_to_do;
317312
/* Which of the old spaces is the visited space */
318313
int visited_space;
319-
int phase;
320314

321315
#ifdef Py_GIL_DISABLED
322316
/* This is the number of objects that survived the last full

Include/internal/pycore_object.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -471,8 +471,8 @@ static inline void _PyObject_GC_TRACK(
471471
PyGC_Head *last = (PyGC_Head*)(generation0->_gc_prev);
472472
_PyGCHead_SET_NEXT(last, gc);
473473
_PyGCHead_SET_PREV(gc, last);
474-
uintptr_t not_visited = 1 ^ interp->gc.visited_space;
475-
gc->_gc_next = ((uintptr_t)generation0) | not_visited;
474+
/* Young objects will be moved into the visited space during GC, so set the bit here */
475+
gc->_gc_next = ((uintptr_t)generation0) | (uintptr_t)interp->gc.visited_space;
476476
generation0->_gc_prev = (uintptr_t)gc;
477477
#endif
478478
}

Include/internal/pycore_runtime_init.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,6 @@ extern PyTypeObject _PyExc_MemoryError;
137137
{ .threshold = 0, }, \
138138
}, \
139139
.work_to_do = -5000, \
140-
.phase = GC_PHASE_MARK, \
141140
}, \
142141
.qsbr = { \
143142
.wr_seq = QSBR_INITIAL, \

InternalDocs/garbage_collector.md

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -477,45 +477,6 @@ specifically in a generation by calling `gc.collect(generation=NUM)`.
477477
```
478478

479479

480-
Optimization: visiting reachable objects
481-
========================================
482-
483-
An object cannot be garbage if it can be reached.
484-
485-
To avoid having to identify reference cycles across the whole heap, we can
486-
reduce the amount of work done considerably by first moving most reachable objects
487-
to the `visited` space. Empirically, most reachable objects can be reached from a
488-
small set of global objects and local variables.
489-
This step does much less work per object, so reduces the time spent
490-
performing garbage collection by at least half.
491-
492-
> [!NOTE]
493-
> Objects that are not determined to be reachable by this pass are not necessarily
494-
> unreachable. We still need to perform the main algorithm to determine which objects
495-
> are actually unreachable.
496-
We use the same technique of forming a transitive closure as the incremental
497-
collector does to find reachable objects, seeding the list with some global
498-
objects and the currently executing frames.
499-
500-
This phase moves objects to the `visited` space, as follows:
501-
502-
1. All objects directly referred to by any builtin class, the `sys` module, the `builtins`
503-
module and all objects directly referred to from stack frames are added to a working
504-
set of reachable objects.
505-
2. Until this working set is empty:
506-
1. Pop an object from the set and move it to the `visited` space
507-
2. For each object directly reachable from that object:
508-
* If it is not already in `visited` space and it is a GC object,
509-
add it to the working set
510-
511-
512-
Before each increment of collection is performed, the stacks are scanned
513-
to check for any new stack frames that have been created since the last
514-
increment. All objects directly referred to from those stack frames are
515-
added to the working set.
516-
Then the above algorithm is repeated, starting from step 2.
517-
518-
519480
Optimization: reusing fields to save memory
520481
===========================================
521482

Lib/test/libregrtest/refleak.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,9 @@ def get_pooled_int(value):
123123
xml_filename = 'refleak-xml.tmp'
124124
result = None
125125
dash_R_cleanup(fs, ps, pic, zdc, abcs)
126+
support.gc_collect()
126127

127128
for i in rep_range:
128-
support.gc_collect()
129129
current = refleak_helper._hunting_for_refleaks
130130
refleak_helper._hunting_for_refleaks = True
131131
try:

Lib/test/test_gc.py

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,6 @@ def __new__(cls, *args, **kwargs):
3131
return C
3232
ContainerNoGC = None
3333

34-
try:
35-
import _testinternalcapi
36-
except ImportError:
37-
_testinternalcapi = None
38-
3934
### Support code
4035
###############################################################################
4136

@@ -1135,7 +1130,6 @@ def setUp(self):
11351130
def tearDown(self):
11361131
gc.disable()
11371132

1138-
@unittest.skipIf(_testinternalcapi is None, "requires _testinternalcapi")
11391133
@requires_gil_enabled("Free threading does not support incremental GC")
11401134
# Use small increments to emulate longer running process in a shorter time
11411135
@gc_threshold(200, 10)
@@ -1173,15 +1167,20 @@ def make_ll(depth):
11731167
enabled = gc.isenabled()
11741168
gc.enable()
11751169
olds = []
1176-
initial_heap_size = _testinternalcapi.get_tracked_heap_size()
11771170
for i in range(20_000):
11781171
newhead = make_ll(20)
11791172
count += 20
11801173
newhead.surprise = head
11811174
olds.append(newhead)
11821175
if len(olds) == 20:
1183-
new_objects = _testinternalcapi.get_tracked_heap_size() - initial_heap_size
1184-
self.assertLess(new_objects, 27_000, f"Heap growing. Reached limit after {i} iterations")
1176+
stats = gc.get_stats()
1177+
young = stats[0]
1178+
incremental = stats[1]
1179+
old = stats[2]
1180+
collected = young['collected'] + incremental['collected'] + old['collected']
1181+
count += CORRECTION
1182+
live = count - collected
1183+
self.assertLess(live, 25000)
11851184
del olds[:]
11861185
if not enabled:
11871186
gc.disable()
@@ -1323,8 +1322,7 @@ def test_refcount_errors(self):
13231322
from test.support import gc_collect, SuppressCrashReport
13241323
13251324
a = [1, 2, 3]
1326-
b = [a, a]
1327-
a.append(b)
1325+
b = [a]
13281326
13291327
# Avoid coredump when Py_FatalError() calls abort()
13301328
SuppressCrashReport().__enter__()
@@ -1334,8 +1332,6 @@ def test_refcount_errors(self):
13341332
# (to avoid deallocating it):
13351333
import ctypes
13361334
ctypes.pythonapi.Py_DecRef(ctypes.py_object(a))
1337-
del a
1338-
del b
13391335
13401336
# The garbage collector should now have a fatal error
13411337
# when it reaches the broken object
@@ -1364,7 +1360,7 @@ def test_refcount_errors(self):
13641360
self.assertRegex(stderr,
13651361
br'object type name: list')
13661362
self.assertRegex(stderr,
1367-
br'object repr : \[1, 2, 3, \[\[...\], \[...\]\]\]')
1363+
br'object repr : \[1, 2, 3\]')
13681364

13691365

13701366
class GCTogglingTests(unittest.TestCase):

Misc/NEWS.d/next/Core_and_Builtins/2024-11-21-16-13-52.gh-issue-126491.0YvL94.rst

Lines changed: 0 additions & 4 deletions
This file was deleted.

Modules/_testinternalcapi.c

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2076,11 +2076,6 @@ has_deferred_refcount(PyObject *self, PyObject *op)
20762076
return PyBool_FromLong(_PyObject_HasDeferredRefcount(op));
20772077
}
20782078

2079-
static PyObject *
2080-
get_tracked_heap_size(PyObject *self, PyObject *Py_UNUSED(ignored))
2081-
{
2082-
return PyLong_FromInt64(PyInterpreterState_Get()->gc.heap_size);
2083-
}
20842079

20852080
static PyMethodDef module_functions[] = {
20862081
{"get_configs", get_configs, METH_NOARGS},
@@ -2179,7 +2174,6 @@ static PyMethodDef module_functions[] = {
21792174
{"get_static_builtin_types", get_static_builtin_types, METH_NOARGS},
21802175
{"identify_type_slot_wrappers", identify_type_slot_wrappers, METH_NOARGS},
21812176
{"has_deferred_refcount", has_deferred_refcount, METH_O},
2182-
{"get_tracked_heap_size", get_tracked_heap_size, METH_NOARGS},
21832177
{NULL, NULL} /* sentinel */
21842178
};
21852179

Python/ceval.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -818,7 +818,6 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
818818
entry_frame.instr_ptr = (_Py_CODEUNIT *)_Py_INTERPRETER_TRAMPOLINE_INSTRUCTIONS + 1;
819819
entry_frame.stackpointer = entry_frame.localsplus;
820820
entry_frame.owner = FRAME_OWNED_BY_CSTACK;
821-
entry_frame.visited = 0;
822821
entry_frame.return_offset = 0;
823822
/* Push frame */
824823
entry_frame.previous = tstate->current_frame;

0 commit comments

Comments
 (0)