Skip to content

Commit ce22895

Browse files
committed
Quickly drop lists full of immortal objects
1 parent 2ff5eb8 commit ce22895

9 files changed

Lines changed: 96 additions & 14 deletions

File tree

Include/cpython/listobject.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,15 @@ static inline Py_ssize_t PyList_GET_SIZE(PyObject *op) {
3939

4040
#define PyList_GET_ITEM(op, index) (_PyList_CAST(op)->ob_item[(index)])
4141

42+
#define _PyList_UPDATE_MORTAL(op, item) ((PyObject *)(op))->ob_flags |= (!_Py_IsImmortal((item))) ? _Py_MORTAL_CHILDREN_FLAG : 0
43+
4244
static inline void
4345
PyList_SET_ITEM(PyObject *op, Py_ssize_t index, PyObject *value) {
4446
PyListObject *list = _PyList_CAST(op);
4547
assert(0 <= index);
4648
assert(index < list->allocated);
4749
list->ob_item[index] = value;
50+
_PyList_UPDATE_MORTAL(op, value);
4851
}
4952
#define PyList_SET_ITEM(op, index, value) \
5053
PyList_SET_ITEM(_PyObject_CAST(op), (index), _PyObject_CAST(value))

Include/internal/pycore_list.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,14 @@ _PyList_AppendTakeRef(PyListObject *self, PyObject *newitem)
4343
PyList_SET_ITEM(self, len, newitem);
4444
#endif
4545
Py_SET_SIZE(self, len + 1);
46+
#ifdef Py_DEBUG
47+
if (!(((PyObject *)self)->ob_flags & _Py_MORTAL_CHILDREN_FLAG)) {
48+
int i = Py_SIZE(self);
49+
while (--i >= 0) {
50+
assert(self->ob_item[i] == NULL || _Py_IsImmortal(self->ob_item[i]));
51+
}
52+
}
53+
#endif
4654
return 0;
4755
}
4856
return _PyList_AppendTakeRefListResize(self, newitem);

Include/refcount.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ cleanup during runtime finalization.
2020
*/
2121

2222
#define _Py_STATICALLY_ALLOCATED_FLAG 4
23+
#define _Py_MORTAL_CHILDREN_FLAG 2
2324
#define _Py_IMMORTAL_FLAGS 1
2425

2526
#if SIZEOF_VOID_P > 4

Lib/test/libregrtest/pgo.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
'test_functools',
2525
'test_generators',
2626
'test_hashlib',
27-
'test_heapq',
27+
# 'test_heapq',
2828
'test_int',
2929
'test_itertools',
3030
'test_json',

Modules/_heapqmodule.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ siftdown(PyListObject *heap, Py_ssize_t startpos, Py_ssize_t pos)
3535
return -1;
3636
}
3737

38+
((PyObject *)heap)->ob_flags |= _Py_MORTAL_CHILDREN_FLAG;
39+
3840
/* Follow the path to the root, moving parents down until finding
3941
a place newitem fits. */
4042
arr = _PyList_ITEMS(heap);
@@ -73,6 +75,8 @@ siftup(PyListObject *heap, Py_ssize_t pos)
7375
PyObject *tmp1, *tmp2, **arr;
7476
int cmp;
7577

78+
((PyObject *)heap)->ob_flags |= _Py_MORTAL_CHILDREN_FLAG;
79+
7680
assert(PyList_Check(heap));
7781
endpos = PyList_GET_SIZE(heap);
7882
startpos = pos;
@@ -400,6 +404,8 @@ siftdown_max(PyListObject *heap, Py_ssize_t startpos, Py_ssize_t pos)
400404
return -1;
401405
}
402406

407+
((PyObject *)heap)->ob_flags |= _Py_MORTAL_CHILDREN_FLAG;
408+
403409
/* Follow the path to the root, moving parents down until finding
404410
a place newitem fits. */
405411
arr = _PyList_ITEMS(heap);
@@ -445,6 +451,8 @@ siftup_max(PyListObject *heap, Py_ssize_t pos)
445451
return -1;
446452
}
447453

454+
((PyObject *)heap)->ob_flags |= _Py_MORTAL_CHILDREN_FLAG;
455+
448456
/* Bubble up the smaller child until hitting a leaf. */
449457
arr = _PyList_ITEMS(heap);
450458
limit = endpos >> 1; /* smallest pos that has no child */

Objects/listobject.c

Lines changed: 72 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,26 @@ list_capacity(PyObject **items)
5555
}
5656
#endif
5757

58+
#ifdef Py_DEBUG
59+
static void validate(PyObject *op)
60+
{
61+
if (op == NULL) {
62+
return;
63+
}
64+
if (((PyListObject *)op)->ob_item == NULL) {
65+
return;
66+
}
67+
if (!(op->ob_flags & _Py_MORTAL_CHILDREN_FLAG)) {
68+
int i = Py_SIZE(op);
69+
while (--i >= 0) {
70+
assert(((PyListObject *)op)->ob_item[i] == NULL || _Py_IsImmortal(((PyListObject *)op)->ob_item[i]));
71+
}
72+
}
73+
}
74+
#else
75+
#define validate(op) ((void)0)
76+
#endif
77+
5878
static void
5979
free_list_items(PyObject** items, bool use_qsbr)
6080
{
@@ -464,6 +484,8 @@ PyList_SetItem(PyObject *op, Py_ssize_t i,
464484
}
465485
PyObject *tmp = self->ob_item[i];
466486
FT_ATOMIC_STORE_PTR_RELEASE(self->ob_item[i], newitem);
487+
_PyList_UPDATE_MORTAL(op, newitem);
488+
validate(op);
467489
Py_XDECREF(tmp);
468490
ret = 0;
469491
end:;
@@ -496,6 +518,8 @@ ins1(PyListObject *self, Py_ssize_t where, PyObject *v)
496518
for (i = n; --i >= where; )
497519
FT_ATOMIC_STORE_PTR_RELAXED(items[i+1], items[i]);
498520
FT_ATOMIC_STORE_PTR_RELEASE(items[where], Py_NewRef(v));
521+
_PyList_UPDATE_MORTAL(self, v);
522+
validate((PyObject *)self);
499523
return 0;
500524
}
501525

@@ -520,11 +544,16 @@ _PyList_AppendTakeRefListResize(PyListObject *self, PyObject *newitem)
520544
{
521545
Py_ssize_t len = Py_SIZE(self);
522546
assert(self->allocated == -1 || self->allocated == len);
547+
548+
validate((PyObject *)self);
549+
523550
if (list_resize(self, len + 1) < 0) {
524551
Py_DECREF(newitem);
525552
return -1;
526553
}
527554
FT_ATOMIC_STORE_PTR_RELEASE(self->ob_item[len], newitem);
555+
_PyList_UPDATE_MORTAL(self, newitem);
556+
validate((PyObject *)self);
528557
return 0;
529558
}
530559

@@ -556,9 +585,11 @@ list_dealloc(PyObject *self)
556585
There's a simple test case where somehow this reduces
557586
thrashing when a *very* large list is created and
558587
immediately deleted. */
559-
i = Py_SIZE(op);
560-
while (--i >= 0) {
561-
Py_XDECREF(op->ob_item[i]);
588+
if (!PyList_CheckExact(self) || self->ob_flags & _Py_MORTAL_CHILDREN_FLAG) {
589+
i = Py_SIZE(op);
590+
while (--i >= 0) {
591+
Py_XDECREF(op->ob_item[i]);
592+
}
562593
}
563594
free_list_items(op->ob_item, false);
564595
op->ob_item = NULL;
@@ -700,8 +731,10 @@ list_slice_lock_held(PyListObject *a, Py_ssize_t ilow, Py_ssize_t ihigh)
700731
for (i = 0; i < len; i++) {
701732
PyObject *v = src[i];
702733
dest[i] = Py_NewRef(v);
734+
_PyList_UPDATE_MORTAL(np, v);
703735
}
704736
Py_SET_SIZE(np, len);
737+
validate((PyObject *)np);
705738
return (PyObject *)np;
706739
}
707740

@@ -752,14 +785,20 @@ list_concat_lock_held(PyListObject *a, PyListObject *b)
752785
for (i = 0; i < Py_SIZE(a); i++) {
753786
PyObject *v = src[i];
754787
dest[i] = Py_NewRef(v);
788+
_PyList_UPDATE_MORTAL(np, v);
755789
}
756790
src = b->ob_item;
757791
dest = np->ob_item + Py_SIZE(a);
758792
for (i = 0; i < Py_SIZE(b); i++) {
759793
PyObject *v = src[i];
760794
dest[i] = Py_NewRef(v);
795+
_PyList_UPDATE_MORTAL(np, v);
761796
}
762797
Py_SET_SIZE(np, size);
798+
799+
// TODO: Why doesn't this work?
800+
// ((PyObject *)np)->ob_flags |= ((((PyObject *)a)->ob_flags | ((PyObject *)b)->ob_flags) & _Py_MORTAL_CHILDREN_FLAG);
801+
validate((PyObject *)np);
763802
return (PyObject *)np;
764803
}
765804

@@ -818,8 +857,10 @@ list_repeat_lock_held(PyListObject *a, Py_ssize_t n)
818857
_Py_memory_repeat((char *)np->ob_item, sizeof(PyObject *)*output_size,
819858
sizeof(PyObject *)*input_size);
820859
}
860+
((PyObject *)np)->ob_flags |= (((PyObject *)a)->ob_flags & _Py_MORTAL_CHILDREN_FLAG);
821861

822862
Py_SET_SIZE(np, output_size);
863+
validate((PyObject *)np);
823864
return (PyObject *) np;
824865
}
825866

@@ -848,8 +889,11 @@ list_clear_impl(PyListObject *a, bool is_resize)
848889
Py_SET_SIZE(a, 0);
849890
FT_ATOMIC_STORE_PTR_RELEASE(a->ob_item, NULL);
850891
a->allocated = 0;
851-
while (--i >= 0) {
852-
Py_XDECREF(items[i]);
892+
if (((PyObject *)a)->ob_flags & _Py_MORTAL_CHILDREN_FLAG) {
893+
while (--i >= 0) {
894+
Py_XDECREF(items[i]);
895+
}
896+
((PyObject *)a)->ob_flags &= ~_Py_MORTAL_CHILDREN_FLAG;
853897
}
854898
#ifdef Py_GIL_DISABLED
855899
if (is_resize) {
@@ -958,6 +1002,8 @@ list_ass_slice_lock_held(PyListObject *a, Py_ssize_t ilow, Py_ssize_t ihigh, PyO
9581002
goto Error;
9591003
}
9601004
item = a->ob_item;
1005+
// Technically we could check if all the remaining items are immortal,
1006+
// but that's an O(n) operation.
9611007
}
9621008
else if (d > 0) { /* Insert d items */
9631009
k = Py_SIZE(a);
@@ -972,6 +1018,7 @@ list_ass_slice_lock_held(PyListObject *a, Py_ssize_t ilow, Py_ssize_t ihigh, PyO
9721018
for (k = 0; k < n; k++, ilow++) {
9731019
PyObject *w = vitem[k];
9741020
FT_ATOMIC_STORE_PTR_RELEASE(item[ilow], Py_XNewRef(w));
1021+
_PyList_UPDATE_MORTAL(a, w);
9751022
}
9761023
for (k = norig - 1; k >= 0; --k)
9771024
Py_XDECREF(recycle[k]);
@@ -980,6 +1027,7 @@ list_ass_slice_lock_held(PyListObject *a, Py_ssize_t ilow, Py_ssize_t ihigh, PyO
9801027
if (recycle != recycle_on_stack)
9811028
PyMem_Free(recycle);
9821029
Py_XDECREF(v_as_SF);
1030+
validate((PyObject *)a);
9831031
return result;
9841032
#undef b
9851033
}
@@ -1048,8 +1096,10 @@ list_inplace_repeat_lock_held(PyListObject *self, Py_ssize_t n)
10481096
}
10491097

10501098
PyObject **items = self->ob_item;
1051-
for (Py_ssize_t j = 0; j < input_size; j++) {
1052-
_Py_RefcntAdd(items[j], n-1);
1099+
if (((PyObject *)self)->ob_flags & _Py_MORTAL_CHILDREN_FLAG) {
1100+
for (Py_ssize_t j = 0; j < input_size; j++) {
1101+
_Py_RefcntAdd(items[j], n-1);
1102+
}
10531103
}
10541104
// TODO: _Py_memory_repeat calls are not safe for shared lists in
10551105
// GIL_DISABLED builds. (See issue #129069)
@@ -1092,6 +1142,8 @@ list_ass_item_lock_held(PyListObject *a, Py_ssize_t i, PyObject *v)
10921142
}
10931143
else {
10941144
FT_ATOMIC_STORE_PTR_RELEASE(a->ob_item[i], Py_NewRef(v));
1145+
_PyList_UPDATE_MORTAL(a, v);
1146+
validate((PyObject *)a);
10951147
}
10961148
Py_DECREF(tmp);
10971149
return 0;
@@ -1212,7 +1264,9 @@ list_extend_fast(PyListObject *self, PyObject *iterable)
12121264
for (Py_ssize_t i = 0; i < n; i++) {
12131265
PyObject *o = src[i];
12141266
FT_ATOMIC_STORE_PTR_RELEASE(dest[i], Py_NewRef(o));
1267+
_PyList_UPDATE_MORTAL(self, o);
12151268
}
1269+
validate((PyObject *)self);
12161270
return 0;
12171271
}
12181272

@@ -1269,7 +1323,9 @@ list_extend_iter_lock_held(PyListObject *self, PyObject *iterable)
12691323
if (Py_SIZE(self) < self->allocated) {
12701324
Py_ssize_t len = Py_SIZE(self);
12711325
FT_ATOMIC_STORE_PTR_RELEASE(self->ob_item[len], item); // steals item ref
1326+
_PyList_UPDATE_MORTAL(self, item);
12721327
Py_SET_SIZE(self, len + 1);
1328+
validate((PyObject *)self);
12731329
}
12741330
else {
12751331
if (_PyList_AppendTakeRef(self, item) < 0)
@@ -1319,9 +1375,11 @@ list_extend_set(PyListObject *self, PySetObject *other)
13191375
PyObject **dest = self->ob_item + m;
13201376
while (_PySet_NextEntryRef((PyObject *)other, &setpos, &key, &hash)) {
13211377
FT_ATOMIC_STORE_PTR_RELEASE(*dest, key);
1378+
_PyList_UPDATE_MORTAL(self, key);
13221379
dest++;
13231380
}
13241381
Py_SET_SIZE(self, m + n);
1382+
validate((PyObject *)self);
13251383
return 0;
13261384
}
13271385

@@ -1342,10 +1400,12 @@ list_extend_dict(PyListObject *self, PyDictObject *dict, int which_item)
13421400
PyObject *obj = keyvalue[which_item];
13431401
Py_INCREF(obj);
13441402
FT_ATOMIC_STORE_PTR_RELEASE(*dest, obj);
1403+
_PyList_UPDATE_MORTAL(self, obj);
13451404
dest++;
13461405
}
13471406

13481407
Py_SET_SIZE(self, m + n);
1408+
validate((PyObject *)self);
13491409
return 0;
13501410
}
13511411

@@ -1369,11 +1429,13 @@ list_extend_dictitems(PyListObject *self, PyDictObject *dict)
13691429
return -1;
13701430
}
13711431
FT_ATOMIC_STORE_PTR_RELEASE(*dest, item);
1432+
_PyList_UPDATE_MORTAL(self, item);
13721433
dest++;
13731434
i++;
13741435
}
13751436

13761437
Py_SET_SIZE(self, m + n);
1438+
validate((PyObject *)self);
13771439
return 0;
13781440
}
13791441

@@ -3221,6 +3283,8 @@ _PyList_AsTupleAndClear(PyListObject *self)
32213283
Py_SET_SIZE(self, 0);
32223284
ret = _PyTuple_FromArraySteal(items, size);
32233285
free_list_items(items, false);
3286+
((PyObject *)self)->ob_flags &= ~_Py_MORTAL_CHILDREN_FLAG;
3287+
validate((PyObject *)self);
32243288
Py_END_CRITICAL_SECTION();
32253289
return ret;
32263290
}
@@ -3240,6 +3304,7 @@ _PyList_FromStackRefStealOnSuccess(const _PyStackRef *src, Py_ssize_t n)
32403304
PyObject **dst = list->ob_item;
32413305
for (Py_ssize_t i = 0; i < n; i++) {
32423306
dst[i] = PyStackRef_AsPyObjectSteal(src[i]);
3307+
_PyList_UPDATE_MORTAL(list, dst[i]);
32433308
}
32443309

32453310
return (PyObject *)list;

Python/bytecodes.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1074,8 +1074,7 @@ dummy_func(
10741074
STAT_INC(STORE_SUBSCR, hit);
10751075

10761076
PyObject *old_value = PyList_GET_ITEM(list, index);
1077-
FT_ATOMIC_STORE_PTR_RELEASE(_PyList_ITEMS(list)[index],
1078-
PyStackRef_AsPyObjectSteal(value));
1077+
PyList_SET_ITEM(list, index, PyStackRef_AsPyObjectSteal(value));
10791078
assert(old_value != NULL);
10801079
UNLOCK_OBJECT(list); // unlock before decrefs!
10811080
PyStackRef_CLOSE_SPECIALIZED(sub_st, _PyLong_ExactDealloc);

Python/executor_cases.c.h

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

Python/generated_cases.c.h

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

0 commit comments

Comments
 (0)