Skip to content

Commit 9d28e99

Browse files
committed
Attempt to match 3.12 in work done in GC
1 parent 2c472d3 commit 9d28e99

File tree

4 files changed

+28
-33
lines changed

4 files changed

+28
-33
lines changed

Include/internal/pycore_gc.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,7 @@ struct _gc_runtime_state {
351351
<0: suppressed; don't immortalize objects */
352352
int immortalize;
353353
#endif
354+
Py_ssize_t prior_heap_size;
354355
};
355356

356357
#ifdef Py_GIL_DISABLED

Lib/test/test_gc.py

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import _testcapi
2323
from _testcapi import with_tp_del
2424
from _testcapi import ContainerNoGC
25+
import _testinternalcapi
2526
except ImportError:
2627
_testcapi = None
2728
def with_tp_del(cls):
@@ -1101,32 +1102,19 @@ def make_ll(depth):
11011102
return head
11021103

11031104
head = make_ll(1000)
1104-
count = 1000
1105-
1106-
# There will be some objects we aren't counting,
1107-
# e.g. the gc stats dicts. This test checks
1108-
# that the counts don't grow, so we try to
1109-
# correct for the uncounted objects
1110-
# This is just an estimate.
1111-
CORRECTION = 20
11121105

11131106
enabled = gc.isenabled()
11141107
gc.enable()
11151108
olds = []
1109+
gc.collect()
1110+
baseline_live = _testinternalcapi.get_heap_size()
11161111
for i in range(20_000):
11171112
newhead = make_ll(20)
1118-
count += 20
11191113
newhead.surprise = head
11201114
olds.append(newhead)
11211115
if len(olds) == 20:
1122-
stats = gc.get_stats()
1123-
young = stats[0]
1124-
incremental = stats[1]
1125-
old = stats[2]
1126-
collected = young['collected'] + incremental['collected'] + old['collected']
1127-
count += CORRECTION
1128-
live = count - collected
1129-
self.assertLess(live, 25000)
1116+
live = _testinternalcapi.get_heap_size()
1117+
self.assertLess(live-baseline_live, 25000)
11301118
del olds[:]
11311119
if not enabled:
11321120
gc.disable()

Modules/_testinternalcapi.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2048,6 +2048,11 @@ identify_type_slot_wrappers(PyObject *self, PyObject *Py_UNUSED(ignored))
20482048
return _PyType_GetSlotWrapperNames();
20492049
}
20502050

2051+
static PyObject *
2052+
get_heap_size(PyObject *self, PyObject *Py_UNUSED(ignored))
2053+
{
2054+
return PyLong_FromSsize_t(PyInterpreterState_Get()->gc.heap_size);
2055+
}
20512056

20522057
static PyMethodDef module_functions[] = {
20532058
{"get_configs", get_configs, METH_NOARGS},
@@ -2145,6 +2150,7 @@ static PyMethodDef module_functions[] = {
21452150
GH_119213_GETARGS_METHODDEF
21462151
{"get_static_builtin_types", get_static_builtin_types, METH_NOARGS},
21472152
{"identify_type_slot_wrappers", identify_type_slot_wrappers, METH_NOARGS},
2153+
{"get_heap_size", get_heap_size, METH_NOARGS, NULL},
21482154
{NULL, NULL} /* sentinel */
21492155
};
21502156

Python/gc.c

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ _PyGC_Init(PyInterpreterState *interp)
182182
return _PyStatus_NO_MEMORY();
183183
}
184184
gcstate->heap_size = 0;
185+
gcstate->prior_heap_size = 0;
185186

186187
return _PyStatus_OK();
187188
}
@@ -1278,19 +1279,16 @@ gc_list_set_space(PyGC_Head *list, int space)
12781279
* the incremental collector must progress through the old
12791280
* space faster than objects are added to the old space.
12801281
*
1281-
* Each young or incremental collection adds a number of
1282-
* objects, S (for survivors) to the old space, and
1283-
* incremental collectors scan I objects from the old space.
1284-
* I > S must be true. We also want I > S * N to be where
1285-
* N > 1. Higher values of N mean that the old space is
1286-
* scanned more rapidly.
1287-
* The default incremental threshold of 10 translates to
1288-
* N == 1.4 (1 + 4/threshold)
1282+
* To do this we maintain a prior heap size, so the
1283+
* change in heap size can easily be computed.
1284+
*
1285+
* Each increment scans twice the delta (if increasing)
1286+
* plus half the size of the young generation.
12891287
*/
12901288

1291-
/* Divide by 10, so that the default incremental threshold of 10
1292-
* scans objects at 1% of the heap size */
1293-
#define SCAN_RATE_DIVISOR 10
1289+
/* Multiply by 5, so that the default incremental threshold of 10
1290+
* scans objects at half the rate as the young generation */
1291+
#define SCAN_RATE_MULTIPLIER 20
12941292

12951293
static void
12961294
add_stats(GCState *gcstate, int gen, struct gc_collection_stats *stats)
@@ -1344,7 +1342,6 @@ gc_collect_young(PyThreadState *tstate,
13441342
if (scale_factor < 1) {
13451343
scale_factor = 1;
13461344
}
1347-
gcstate->work_to_do += gcstate->heap_size / SCAN_RATE_DIVISOR / scale_factor;
13481345
add_stats(gcstate, 0, stats);
13491346
}
13501347

@@ -1446,9 +1443,6 @@ gc_collect_increment(PyThreadState *tstate, struct gc_collection_stats *stats)
14461443
if (scale_factor < 1) {
14471444
scale_factor = 1;
14481445
}
1449-
gc_list_merge(&gcstate->young.head, &increment);
1450-
gcstate->young.count = 0;
1451-
gc_list_validate_space(&increment, gcstate->visited_space);
14521446
Py_ssize_t increment_size = 0;
14531447
while (increment_size < gcstate->work_to_do) {
14541448
if (gc_list_is_empty(not_visited)) {
@@ -1467,7 +1461,12 @@ gc_collect_increment(PyThreadState *tstate, struct gc_collection_stats *stats)
14671461
gc_list_validate_space(&survivors, gcstate->visited_space);
14681462
gc_list_merge(&survivors, visited);
14691463
assert(gc_list_is_empty(&increment));
1470-
gcstate->work_to_do += gcstate->heap_size / SCAN_RATE_DIVISOR / scale_factor;
1464+
Py_ssize_t delta = gcstate->heap_size - gcstate->prior_heap_size;
1465+
delta += gcstate->young.threshold * SCAN_RATE_MULTIPLIER / scale_factor;
1466+
if (delta > 0) {
1467+
gcstate->work_to_do += delta;
1468+
}
1469+
gcstate->prior_heap_size = gcstate->heap_size;
14711470
gcstate->work_to_do -= increment_size;
14721471

14731472
validate_old(gcstate);
@@ -1856,6 +1855,7 @@ _PyGC_Collect(PyThreadState *tstate, int generation, _PyGC_Reason reason)
18561855
gc_collect_young(tstate, &stats);
18571856
break;
18581857
case 1:
1858+
gc_collect_young(tstate, &stats);
18591859
gc_collect_increment(tstate, &stats);
18601860
break;
18611861
case 2:

0 commit comments

Comments
 (0)