Skip to content

Commit 556c016

Browse files
committed
gh-112087: Use QSBR technique for list_new/clear for free-thread
1 parent 200271c commit 556c016

File tree

2 files changed

+116
-58
lines changed

2 files changed

+116
-58
lines changed

Lib/test/test_list.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import sys
22
from test import list_tests
3-
from test.support import cpython_only
3+
from test.support import cpython_only, Py_GIL_DISABLED
44
import pickle
55
import unittest
66

@@ -230,6 +230,7 @@ def __eq__(self, other):
230230
self.assertFalse(list3 == list4)
231231

232232
@cpython_only
233+
@unittest.skipIf(Py_GIL_DISABLED, 'Only for the default build')
233234
def test_preallocation(self):
234235
iterable = [0] * 10
235236
iter_size = sys.getsizeof(iterable)

Objects/listobject.c

+114-57
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,91 @@ get_list_freelist(void)
3131
}
3232
#endif
3333

34+
#ifdef Py_GIL_DISABLED
35+
static size_t
36+
list_good_size(Py_ssize_t size)
37+
{
38+
// 4, 8, 16, 24, 32, 40, 48, 64, 80, ...
39+
// NOTE: we add one here so that the rounding accounts for the "allocated"
40+
size_t reqsize = (size_t)size + 1;
41+
if (reqsize <= 4) {
42+
reqsize = 4;
43+
}
44+
else if (reqsize <= 48) {
45+
reqsize = (reqsize + 7) & ~7;
46+
}
47+
else {
48+
reqsize = (reqsize + 15) & ~15;
49+
if (reqsize <= MI_MEDIUM_OBJ_WSIZE_MAX) {
50+
reqsize = mi_good_size(reqsize * sizeof(PyObject *))/sizeof(PyObject*);
51+
}
52+
else {
53+
// ensure geometric spacing for large arrays
54+
size_t shift = mi_bsr(reqsize) - 2;
55+
reqsize = ((reqsize >> shift) + 1) << shift;
56+
}
57+
}
58+
return reqsize - 1;
59+
}
60+
61+
static PyObject**
62+
list_allocate_items(size_t capacity)
63+
{
64+
if (capacity > PY_SSIZE_T_MAX / sizeof(PyObject *) - 1) {
65+
return NULL;
66+
}
67+
PyObject **items = PyMem_Malloc(capacity * sizeof(PyObject *));
68+
return items;
69+
}
70+
#endif
71+
72+
static PyListObject *
73+
list_new(Py_ssize_t size)
74+
{
75+
PyListObject *op;
76+
assert(size >= 0);
77+
#ifdef WITH_FREELISTS
78+
struct _Py_list_freelist *list_freelist = get_list_freelist();
79+
if (PyList_MAXFREELIST && list_freelist->numfree > 0) {
80+
list_freelist->numfree--;
81+
op = list_freelist->items[list_freelist->numfree];
82+
OBJECT_STAT_INC(from_freelist);
83+
_Py_NewReference((PyObject *)op);
84+
}
85+
else
86+
#endif
87+
{
88+
op = PyObject_GC_New(PyListObject, &PyList_Type);
89+
if (op == NULL) {
90+
return NULL;
91+
}
92+
}
93+
if (size <= 0) {
94+
op->ob_item = NULL;
95+
op->allocated = 0;
96+
}
97+
else {
98+
#ifdef Py_GIL_DISABLED
99+
size_t capacity = list_good_size(size);
100+
PyObject **items = list_allocate_items(capacity);
101+
#else
102+
size_t capacity = size;
103+
PyObject **items = (PyObject **) PyMem_Calloc(size, sizeof(PyObject *));
104+
#endif
105+
if (items == NULL) {
106+
op->ob_item = NULL;
107+
Py_DECREF(op);
108+
PyErr_NoMemory();
109+
return NULL;
110+
}
111+
op->ob_item = items;
112+
op->allocated = capacity;
113+
}
114+
Py_SET_SIZE(op, size);
115+
_PyObject_GC_TRACK(op);
116+
return op;
117+
}
118+
34119
/* Ensure ob_item has room for at least newsize elements, and set
35120
* ob_size to newsize. If newsize > ob_size on entry, the content
36121
* of the new slots at exit is undefined heap trash; it's the caller's
@@ -151,61 +236,18 @@ _PyList_DebugMallocStats(FILE *out)
151236
PyObject *
152237
PyList_New(Py_ssize_t size)
153238
{
154-
PyListObject *op;
155-
156239
if (size < 0) {
157240
PyErr_BadInternalCall();
158241
return NULL;
159242
}
160-
161-
#ifdef WITH_FREELISTS
162-
struct _Py_list_freelist *list_freelist = get_list_freelist();
163-
if (PyList_MAXFREELIST && list_freelist->numfree > 0) {
164-
list_freelist->numfree--;
165-
op = list_freelist->items[list_freelist->numfree];
166-
OBJECT_STAT_INC(from_freelist);
167-
_Py_NewReference((PyObject *)op);
168-
}
169-
else
170-
#endif
171-
{
172-
op = PyObject_GC_New(PyListObject, &PyList_Type);
173-
if (op == NULL) {
174-
return NULL;
175-
}
176-
}
177-
if (size <= 0) {
178-
op->ob_item = NULL;
179-
}
180-
else {
181-
op->ob_item = (PyObject **) PyMem_Calloc(size, sizeof(PyObject *));
182-
if (op->ob_item == NULL) {
183-
Py_DECREF(op);
184-
return PyErr_NoMemory();
243+
PyListObject *op = list_new(size);
244+
if (op && op->ob_item) {
245+
PyObject **items = op->ob_item;
246+
for (Py_ssize_t i = 0, n = op->allocated; i < n; i++) {
247+
FT_ATOMIC_STORE_PTR_RELEASE(items[i], NULL);
185248
}
186249
}
187-
Py_SET_SIZE(op, size);
188-
op->allocated = size;
189-
_PyObject_GC_TRACK(op);
190-
return (PyObject *) op;
191-
}
192-
193-
static PyObject *
194-
list_new_prealloc(Py_ssize_t size)
195-
{
196-
assert(size > 0);
197-
PyListObject *op = (PyListObject *) PyList_New(0);
198-
if (op == NULL) {
199-
return NULL;
200-
}
201-
assert(op->ob_item == NULL);
202-
op->ob_item = PyMem_New(PyObject *, size);
203-
if (op->ob_item == NULL) {
204-
Py_DECREF(op);
205-
return PyErr_NoMemory();
206-
}
207-
op->allocated = size;
208-
return (PyObject *) op;
250+
return (PyObject *)op;
209251
}
210252

211253
Py_ssize_t
@@ -515,7 +557,7 @@ list_slice(PyListObject *a, Py_ssize_t ilow, Py_ssize_t ihigh)
515557
if (len <= 0) {
516558
return PyList_New(0);
517559
}
518-
np = (PyListObject *) list_new_prealloc(len);
560+
np = (PyListObject *) list_new(len);
519561
if (np == NULL)
520562
return NULL;
521563

@@ -567,7 +609,7 @@ list_concat_lock_held(PyListObject *a, PyListObject *b)
567609
if (size == 0) {
568610
return PyList_New(0);
569611
}
570-
np = (PyListObject *) list_new_prealloc(size);
612+
np = (PyListObject *) list_new(size);
571613
if (np == NULL) {
572614
return NULL;
573615
}
@@ -617,7 +659,7 @@ list_repeat_lock_held(PyListObject *a, Py_ssize_t n)
617659
return PyErr_NoMemory();
618660
Py_ssize_t output_size = input_size * n;
619661

620-
PyListObject *np = (PyListObject *) list_new_prealloc(output_size);
662+
PyListObject *np = (PyListObject *) list_new(output_size);
621663
if (np == NULL)
622664
return NULL;
623665

@@ -658,7 +700,7 @@ list_repeat(PyObject *aa, Py_ssize_t n)
658700
}
659701

660702
static void
661-
list_clear(PyListObject *a)
703+
list_clear_impl(PyListObject *a, bool is_resize)
662704
{
663705
PyObject **items = a->ob_item;
664706
if (items == NULL) {
@@ -675,16 +717,31 @@ list_clear(PyListObject *a)
675717
Py_XDECREF(items[i]);
676718
}
677719
// TODO: Use QSBR technique, if the list is shared between threads,
678-
PyMem_Free(items);
679-
720+
#ifdef Py_GIL_DISABLED
721+
bool use_qsbr = is_resize && _PyObject_GC_IS_SHARED(a);
722+
#else
723+
bool use_qsbr = false;
724+
#endif
725+
if (use_qsbr) {
726+
_PyMem_FreeDelayed(items);
727+
}
728+
else {
729+
PyMem_Free(items);
730+
}
680731
// Note that there is no guarantee that the list is actually empty
681732
// at this point, because XDECREF may have populated it indirectly again!
682733
}
683734

735+
static void
736+
list_clear(PyListObject *a)
737+
{
738+
list_clear_impl(a, true);
739+
}
740+
684741
static int
685742
list_clear_slot(PyObject *self)
686743
{
687-
list_clear((PyListObject *)self);
744+
list_clear_impl((PyListObject *)self, false);
688745
return 0;
689746
}
690747

@@ -3095,7 +3152,7 @@ list_subscript(PyObject* _self, PyObject* item)
30953152
return list_slice(self, start, stop);
30963153
}
30973154
else {
3098-
result = list_new_prealloc(slicelength);
3155+
result = (PyObject *)list_new(slicelength);
30993156
if (!result) return NULL;
31003157

31013158
src = self->ob_item;

0 commit comments

Comments
 (0)