Skip to content

Commit 0c2a852

Browse files
committed
Finish() shrinks the frozenset if needed
1 parent 559664f commit 0c2a852

3 files changed

Lines changed: 53 additions & 5 deletions

File tree

‎Lib/test/test_capi/test_set.py‎

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -296,14 +296,19 @@ def assert_unhashable(self):
296296
return self.assertRaisesRegex(TypeError, err_msg)
297297

298298
def test_empty(self):
299-
for size in (0, 128):
299+
_set_get_table_size = _testcapi._set_get_table_size
300+
301+
for size in (0, 1024):
300302
with self.subTest(size=size):
301303
writer = self.create_writer(size)
302304
empty_tuple = ()
303305
writer.update(empty_tuple)
304306
writer.update_array(empty_tuple)
305307
writer.update_array_steal(empty_tuple)
306-
self.assertEqual(writer.finish(), frozenset())
308+
result = writer.finish()
309+
self.assertEqual(result, frozenset())
310+
self.assertEqual(_set_get_table_size(result),
311+
self.PySet_MINSIZE)
307312

308313
def test_get_table_set(self):
309314
# Test size parameter of PyFrozenSetWriter_Create()
@@ -420,6 +425,24 @@ class MyObject:
420425
writer.update([obj])
421426
self.assertTrue(gc.is_tracked(writer.finish()))
422427

428+
def test_finish_shrink(self):
429+
# Check that PyFrozenSetWriter_Finish() shrinks the table
430+
# if the frozenset table is way too big
431+
_set_get_table_size = _testcapi._set_get_table_size
432+
433+
writer = self.create_writer(1024)
434+
writer.update([1])
435+
result = writer.finish()
436+
self.assertEqual(result, frozenset([1]))
437+
self.assertEqual(_set_get_table_size(result), self.PySet_MINSIZE)
438+
439+
items = self.create_items(127)
440+
writer = self.create_writer(len(items) * 100)
441+
writer.update(items)
442+
result = writer.finish()
443+
self.assertEqual(result, frozenset(items))
444+
self.assertEqual(_set_get_table_size(result), 256)
445+
423446
def test_discard(self):
424447
# Test PyFrozenSetWriter_Discard(): must not leak memory
425448

‎Modules/_testcapi/set.c‎

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,15 @@ test_set_type_size(PyObject *self, PyObject *Py_UNUSED(ignored))
3131
}
3232

3333

34+
static PyObject*
35+
_set_get_table_size(PyObject *self_raw, PyObject *arg)
36+
{
37+
PySetObject *frozenset = _PySet_CAST(arg);
38+
Py_ssize_t table_size = frozenset->mask + 1;
39+
return PyLong_FromSsize_t(table_size);
40+
}
41+
42+
3443
typedef struct {
3544
PyObject_HEAD
3645
PyFrozenSetWriter *writer;
@@ -210,9 +219,8 @@ writer_get_table_size(PyObject *self_raw, PyObject *Py_UNUSED(args))
210219
return NULL;
211220
}
212221

213-
PySetObject *frozenset = _PySet_CAST(self->writer);
214-
Py_ssize_t table_size = frozenset->mask + 1;
215-
return PyLong_FromSsize_t(table_size);
222+
PyObject *frozenset = _PyObject_CAST(self->writer);
223+
return _set_get_table_size(self_raw, frozenset);
216224
}
217225

218226

@@ -277,6 +285,7 @@ static PyType_Spec Writer_spec = {
277285
static PyMethodDef test_methods[] = {
278286
{"set_get_size", set_get_size, METH_O},
279287
{"test_set_type_size", test_set_type_size, METH_NOARGS},
288+
{"_set_get_table_size", _set_get_table_size, METH_O},
280289
{NULL},
281290
};
282291

‎Objects/setobject.c‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3289,6 +3289,22 @@ PyFrozenSetWriter_Finish(PyFrozenSetWriter *writer)
32893289
assert(frozensetwriter_check_consistency(writer));
32903290
PySetObject *frozenset = _PySet_CAST(writer);
32913291

3292+
// If the frozenset table is too big (waste memory), make the table
3293+
// smaller. For example, PyFrozenSetWriter_Create() argument was way too
3294+
// big.
3295+
Py_ssize_t table_size = frozenset->mask + 1;
3296+
// set_add_entry_takeref() allocates a table of used*4 for table smaller
3297+
// than 50,000 items. So check for table_size / 8.
3298+
if (frozenset->used < table_size / 8
3299+
&& frozenset->table != frozenset->smalltable)
3300+
{
3301+
if (set_table_resize(frozenset, frozenset->used * 2) != 0) {
3302+
PyFrozenSetWriter_Discard(writer);
3303+
return NULL;
3304+
}
3305+
assert((frozenset->mask + 1) != table_size);
3306+
}
3307+
32923308
// Now that the frozenset is fully initialized, it's safe to track it in
32933309
// the GC. Track the frozenset if at least one item is tracked by the GC.
32943310
Py_ssize_t pos = 0;

0 commit comments

Comments
 (0)