diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-08-29-04-42-02.gh-issue-156548.ywUwcn.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-29-04-42-02.gh-issue-156548.ywUwcn.rst new file mode 100644 index 00000000000000..3dce3b5f2a237a --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-08-29-04-42-02.gh-issue-156548.ywUwcn.rst @@ -0,0 +1 @@ +Optimize set(), frozenset(), and set.update(). diff --git a/Objects/setobject.c b/Objects/setobject.c index 8fdd1eb26118c0..201da749787263 100644 --- a/Objects/setobject.c +++ b/Objects/setobject.c @@ -562,6 +562,18 @@ set_table_resize(PySetObject *so, Py_ssize_t minused) return 0; } +/* +Resize at the start of an operation where the final maximum set size is known +*/ +static int +set_presize(PySetObject *so, Py_ssize_t n) +{ + if ((so->fill + n)*5 >= so->mask*3) { + return set_table_resize(so, (so->used + n)*2); + } + return 0; +} + static int set_contains_entry(PySetObject *so, PyObject *key, Py_hash_t hash) { @@ -842,13 +854,8 @@ set_merge_lock_held(PySetObject *so, PyObject *otherset) if (other == so || other->used == 0) /* a.update(a) or a.update(set()); nothing to do */ return 0; - /* Do one big resize at the start, rather than - * incrementally resizing as we insert new keys. Expect - * that there will be no (or few) overlapping keys. - */ - if ((so->fill + other->used)*5 >= so->mask*3) { - if (set_table_resize(so, (so->used + other->used)*2) != 0) - return -1; + if (set_presize(so, other->used) < 0) { + return -1; } so_entry = so->table; other_entry = other->table; @@ -1195,15 +1202,8 @@ set_update_dict_lock_held(PySetObject *so, PyObject *other) } #endif - /* Do one big resize at the start, rather than - * incrementally resizing as we insert new keys. Expect - * that there will be no (or few) overlapping keys. - */ - Py_ssize_t dictsize = PyDict_GET_SIZE(other); - if ((so->fill + dictsize)*5 >= so->mask*3) { - if (set_table_resize(so, (so->used + dictsize)*2) != 0) { - return -1; - } + if (set_presize(so, PyDict_GET_SIZE(other)) < 0) { + return -1; } Py_ssize_t pos = 0; @@ -1228,6 +1228,19 @@ set_update_iterable_lock_held(PySetObject *so, PyObject *other) return -1; } + Py_ssize_t n = PyObject_LengthHint(other, 0); + if (n < 0) { + PyErr_Clear(); /* grow on demand instead */ + } + else if (n == 0 || n >= PY_SSIZE_T_MAX/8 - so->fill) { + /* Either a length hint was not found or the returned value for `n` + could lead to an overflow */ + } + else if (set_presize(so, n) < 0) { + Py_DECREF(it); + return -1; + } + PyObject *key; while ((key = PyIter_Next(it)) != NULL) { if (set_add_key(so, key)) {