Skip to content

Commit f1992f1

Browse files
committed
Add size parameter to PyFrozenSetWriter_Create()
1 parent dd64229 commit f1992f1

8 files changed

Lines changed: 93 additions & 23 deletions

File tree

‎Doc/c-api/set.rst‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,10 +241,12 @@ See also the :c:func:`PyFrozenSet_New` function.
241241
success, or :c:func:`PyFrozenSetWriter_Discard` on error.
242242
243243
244-
.. c:function:: PyFrozenSetWriter* PyFrozenSetWriter_Create(void)
244+
.. c:function:: PyFrozenSetWriter* PyFrozenSetWriter_Create(Py_ssize_t size)
245245
246246
Create a :c:type:`PyFrozenSetWriter`.
247247
248+
If *size* is greater than zero, preallocate *size* items.
249+
248250
On error, set an exception and return ``NULL``.
249251
250252
*size* must be positive or zero.

‎Include/cpython/setobject.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ static inline Py_ssize_t PySet_GET_SIZE(PyObject *so) {
7575

7676
typedef struct PyFrozenSetWriter PyFrozenSetWriter;
7777

78-
PyAPI_FUNC(PyFrozenSetWriter*) PyFrozenSetWriter_Create(void);
78+
PyAPI_FUNC(PyFrozenSetWriter*) PyFrozenSetWriter_Create(Py_ssize_t size);
7979
PyAPI_FUNC(PyObject*) PyFrozenSetWriter_Finish(PyFrozenSetWriter *writer);
8080
PyAPI_FUNC(void) PyFrozenSetWriter_Discard(PyFrozenSetWriter *writer);
8181
PyAPI_FUNC(int) PyFrozenSetWriter_Add(

‎Lib/test/test_capi/test_set.py‎

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -278,30 +278,67 @@ def test_set_next_entry(self):
278278

279279

280280
class FrozenSetWriterTests(unittest.TestCase):
281-
def create_writer(self):
281+
PySet_MINSIZE = _testcapi.PySet_MINSIZE
282+
283+
def create_writer(self, size=0):
282284
# Test PyFrozenSetWriter_Create()
283-
return _testcapi.PyFrozenSetWriter()
285+
return _testcapi.PyFrozenSetWriter(size)
286+
287+
def compute_table_size(self, size):
288+
table_size = self.PySet_MINSIZE
289+
while table_size <= size:
290+
table_size <<= 1
291+
return table_size
284292

285293
def frozenset_equal(self, result, expected):
286294
if not isinstance(expected, frozenset):
287295
expected = frozenset(expected)
288296
self.assertEqual(result, expected)
289297
self.assertEqual(type(result), frozenset)
290298

299+
def test_get_table_set(self):
300+
# Test size parameter of PyFrozenSetWriter_Create()
301+
PySet_MINSIZE = self.PySet_MINSIZE
302+
303+
def check_table_size(size, table_size):
304+
writer = self.create_writer(size)
305+
self.assertEqual(writer.get_table_size(), table_size)
306+
307+
check_table_size(0, PySet_MINSIZE)
308+
check_table_size(PySet_MINSIZE - 1, PySet_MINSIZE)
309+
check_table_size(PySet_MINSIZE, PySet_MINSIZE * 2)
310+
check_table_size(2 ** 15 - 1, 2 ** 15)
311+
312+
def create_items(self, nitem):
313+
# Use mortal (and unique) objects to be able to detect reference leaks
314+
return tuple(object() for _ in range(nitem))
315+
291316
def test_append(self):
292317
# Test PyFrozenSetWriter_Append()
293-
items = (1, 2, 3)
294-
writer = self.create_writer()
295-
for item in items:
296-
writer.add(item)
297-
self.frozenset_equal(writer.finish(), items)
318+
items = self.create_items(self.PySet_MINSIZE * 2 - 1)
319+
table_size = self.compute_table_size(len(items))
320+
for prealloc in (0, len(items) // 2, len(items), len(items) * 10):
321+
with self.subTest(size=prealloc):
322+
writer = self.create_writer()
323+
for item in items:
324+
writer.add(item)
325+
self.frozenset_equal(writer.finish(), items)
298326

299327
def test_update(self):
300328
# Test PyFrozenSetWriter_Update()
301-
writer = self.create_writer()
302-
writer.update((2, 5))
303-
writer.update((5, 7))
304-
self.frozenset_equal(writer.finish(), {2, 5, 7})
329+
items1 = self.create_items(self.PySet_MINSIZE - 1)
330+
items2 = self.create_items(self.PySet_MINSIZE - 1)
331+
common = self.create_items(self.PySet_MINSIZE - 1)
332+
expected = set(items1) | set(items2) | set(common)
333+
total = len(expected)
334+
self.assertEqual(total, len(items1) + len(items2) + len(common))
335+
336+
for prealloc in (0, total // 2, total, total * 10):
337+
with self.subTest(size=prealloc):
338+
writer = self.create_writer(prealloc)
339+
writer.update(items1 + common)
340+
writer.update(common + items2)
341+
self.frozenset_equal(writer.finish(), expected)
305342

306343
def test_finish(self):
307344
# Test PyFrozenSetWriter_Finish(): track the frozenset in the GC if at

‎Modules/_abc.c‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ static int
362362
compute_abstract_methods(PyObject *self)
363363
{
364364
int ret = -1;
365-
PyFrozenSetWriter *abstracts = PyFrozenSetWriter_Create();
365+
PyFrozenSetWriter *abstracts = PyFrozenSetWriter_Create(0);
366366
if (abstracts == NULL) {
367367
return -1;
368368
}

‎Modules/_asynciomodule.c‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1339,7 +1339,7 @@ _asyncio_Future__asyncio_awaited_by_get_impl(FutureObj *self)
13391339
return PyFrozenSet_New(self->fut_awaited_by);
13401340
}
13411341

1342-
PyFrozenSetWriter *set = PyFrozenSetWriter_Create();
1342+
PyFrozenSetWriter *set = PyFrozenSetWriter_Create(1);
13431343
if (set == NULL) {
13441344
return NULL;
13451345
}

‎Modules/_hashopenssl.c‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2519,7 +2519,7 @@ static int
25192519
hashlib_md_meth_names(PyObject *module)
25202520
{
25212521
_InternalNameMapperState state = {
2522-
.set = PyFrozenSetWriter_Create(),
2522+
.set = PyFrozenSetWriter_Create(0),
25232523
.error = 0
25242524
};
25252525
if (state.set == NULL) {

‎Modules/_testcapi/set.c‎

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,21 +65,21 @@ writer_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
6565
static int
6666
writer_init(PyObject *self_raw, PyObject *args, PyObject *kwargs)
6767
{
68-
if (PyTuple_GET_SIZE(args)) {
69-
PyErr_Format(PyExc_TypeError,
70-
"PyFrozenSetWriter() takes no positional arguments");
71-
return -1;
72-
}
7368
if (kwargs && PyDict_GET_SIZE(kwargs)) {
7469
PyErr_Format(PyExc_TypeError,
7570
"PyFrozenSetWriter() takes no keyword arguments");
7671
return -1;
7772
}
7873

74+
Py_ssize_t size;
75+
if (!PyArg_ParseTuple(args, "n", &size)) {
76+
return -1;
77+
}
78+
7979
WriterObject *self = (WriterObject *)self_raw;
8080
PyFrozenSetWriter_Discard(self->writer);
8181

82-
self->writer = PyFrozenSetWriter_Create();
82+
self->writer = PyFrozenSetWriter_Create(size);
8383
if (self->writer == NULL) {
8484
return -1;
8585
}
@@ -134,6 +134,20 @@ writer_update(PyObject *self_raw, PyObject *iterable)
134134
}
135135

136136

137+
static PyObject*
138+
writer_get_table_size(PyObject *self_raw, PyObject *Py_UNUSED(args))
139+
{
140+
WriterObject *self = (WriterObject *)self_raw;
141+
if (writer_check(self) < 0) {
142+
return NULL;
143+
}
144+
145+
PySetObject *frozenset = _PySet_CAST(self->writer);
146+
Py_ssize_t table_size = frozenset->mask + 1;
147+
return PyLong_FromSsize_t(table_size);
148+
}
149+
150+
137151
// Test PyFrozenSetWriter_Finish()
138152
static PyObject*
139153
writer_finish(PyObject *self_raw, PyObject *Py_UNUSED(args))
@@ -167,6 +181,7 @@ writer_discard(PyObject *self_raw, PyObject *Py_UNUSED(args))
167181
static PyMethodDef writer_methods[] = {
168182
{"add", _PyCFunction_CAST(writer_add), METH_O},
169183
{"update", _PyCFunction_CAST(writer_update), METH_O},
184+
{"get_table_size", _PyCFunction_CAST(writer_get_table_size), METH_NOARGS},
170185
{"finish", _PyCFunction_CAST(writer_finish), METH_NOARGS},
171186
{"discard", _PyCFunction_CAST(writer_discard), METH_NOARGS},
172187
{NULL, NULL} /* sentinel */
@@ -212,5 +227,9 @@ _PyTestCapi_Init_Set(PyObject *m)
212227
}
213228
Py_DECREF(writer_type);
214229

230+
if (PyModule_AddIntMacro(m, PySet_MINSIZE) < 0) {
231+
return -1;
232+
}
233+
215234
return 0;
216235
}

‎Objects/setobject.c‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3243,13 +3243,25 @@ frozsetwriter_check_consistency(PyFrozenSetWriter *writer)
32433243

32443244

32453245
PyFrozenSetWriter*
3246-
PyFrozenSetWriter_Create(void)
3246+
PyFrozenSetWriter_Create(Py_ssize_t size)
32473247
{
3248+
if (size < 0) {
3249+
PyErr_SetString(PyExc_ValueError, "size must be >= 0");
3250+
return NULL;
3251+
}
3252+
32483253
PyObject *frozenset = make_new_set_untracked(&PyFrozenSet_Type, NULL);
32493254
if (frozenset == NULL) {
32503255
return NULL;
32513256
}
32523257

3258+
if (size > 0) {
3259+
if (set_table_resize(_PySet_CAST(frozenset), size) != 0) {
3260+
Py_DECREF(frozenset);
3261+
return NULL;
3262+
}
3263+
}
3264+
32533265
PyFrozenSetWriter *writer = (PyFrozenSetWriter*)frozenset;
32543266
assert(frozsetwriter_check_consistency(writer));
32553267
return writer;

0 commit comments

Comments
 (0)