From d1aaf9aa257b3877425a279ccb6ee984fda6c153 Mon Sep 17 00:00:00 2001 From: alperyoney Date: Tue, 21 Oct 2025 15:07:28 -0700 Subject: [PATCH 1/5] Optimize PySet_Add for uniquely referenced sets in free-threading --- Objects/setobject.c | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/Objects/setobject.c b/Objects/setobject.c index 213bd821d8a1b9..6797979f04ac1a 100644 --- a/Objects/setobject.c +++ b/Objects/setobject.c @@ -2773,17 +2773,23 @@ PySet_Discard(PyObject *set, PyObject *key) int PySet_Add(PyObject *anyset, PyObject *key) { - if (!PySet_Check(anyset) && - (!PyFrozenSet_Check(anyset) || !_PyObject_IsUniquelyReferenced(anyset))) { - PyErr_BadInternalCall(); - return -1; + if (_PyObject_IsUniquelyReferenced(anyset) && PyAnySet_Check(anyset)) { + // In free-threading, if the set or frozenset is uniquely referenced, + // no critical section is needed since only the owner thread is + // populating it. + return set_add_key((PySetObject *)anyset, key); } - int rv; - Py_BEGIN_CRITICAL_SECTION(anyset); - rv = set_add_key((PySetObject *)anyset, key); - Py_END_CRITICAL_SECTION(); - return rv; + if (PySet_Check(anyset)) { + int rv; + Py_BEGIN_CRITICAL_SECTION(anyset); + rv = set_add_key((PySetObject *)anyset, key); + Py_END_CRITICAL_SECTION(); + return rv; + } + + PyErr_BadInternalCall(); + return -1; } int From 37ac4ce442fe622dd41410dabd272cec7026d3af Mon Sep 17 00:00:00 2001 From: alperyoney Date: Wed, 22 Oct 2025 12:19:37 -0700 Subject: [PATCH 2/5] Improve comments --- Objects/setobject.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Objects/setobject.c b/Objects/setobject.c index 6797979f04ac1a..3e9ac6ab9d6269 100644 --- a/Objects/setobject.c +++ b/Objects/setobject.c @@ -2774,9 +2774,9 @@ int PySet_Add(PyObject *anyset, PyObject *key) { if (_PyObject_IsUniquelyReferenced(anyset) && PyAnySet_Check(anyset)) { - // In free-threading, if the set or frozenset is uniquely referenced, - // no critical section is needed since only the owner thread is - // populating it. + // We can only change frozensets if they are uniquely referenced, + // and we can avoid locking sets even in free-threaded build if they + // are uniquely referenced. return set_add_key((PySetObject *)anyset, key); } From 48a4017a4080f028abfd503f25beb63b223d7f4f Mon Sep 17 00:00:00 2001 From: alperyoney Date: Wed, 22 Oct 2025 12:49:28 -0700 Subject: [PATCH 3/5] Add news entry --- .../2025-10-22-12-48-05.gh-issue-140476.F3-d1P.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-10-22-12-48-05.gh-issue-140476.F3-d1P.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-10-22-12-48-05.gh-issue-140476.F3-d1P.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-22-12-48-05.gh-issue-140476.F3-d1P.rst new file mode 100644 index 00000000000000..d9c205da78f5d8 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-22-12-48-05.gh-issue-140476.F3-d1P.rst @@ -0,0 +1,2 @@ +Optimize :c:func:`PySet_Add` for uniquely referenced sets in :term:`free +threaded ` build. From 0932e1641181891ca904149183f8b4dc1508740f Mon Sep 17 00:00:00 2001 From: alperyoney Date: Thu, 23 Oct 2025 13:15:24 -0700 Subject: [PATCH 4/5] Only for frozenset --- ...2025-10-22-12-48-05.gh-issue-140476.F3-d1P.rst | 4 ++-- Objects/setobject.c | 15 ++++++++------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-10-22-12-48-05.gh-issue-140476.F3-d1P.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-22-12-48-05.gh-issue-140476.F3-d1P.rst index d9c205da78f5d8..a24033208c558c 100644 --- a/Misc/NEWS.d/next/Core_and_Builtins/2025-10-22-12-48-05.gh-issue-140476.F3-d1P.rst +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-10-22-12-48-05.gh-issue-140476.F3-d1P.rst @@ -1,2 +1,2 @@ -Optimize :c:func:`PySet_Add` for uniquely referenced sets in :term:`free -threaded ` build. +Optimize :c:func:`PySet_Add` for :class:`frozenset` in :term:`free threaded +` build. diff --git a/Objects/setobject.c b/Objects/setobject.c index 3e9ac6ab9d6269..556063619db39f 100644 --- a/Objects/setobject.c +++ b/Objects/setobject.c @@ -2773,13 +2773,6 @@ PySet_Discard(PyObject *set, PyObject *key) int PySet_Add(PyObject *anyset, PyObject *key) { - if (_PyObject_IsUniquelyReferenced(anyset) && PyAnySet_Check(anyset)) { - // We can only change frozensets if they are uniquely referenced, - // and we can avoid locking sets even in free-threaded build if they - // are uniquely referenced. - return set_add_key((PySetObject *)anyset, key); - } - if (PySet_Check(anyset)) { int rv; Py_BEGIN_CRITICAL_SECTION(anyset); @@ -2788,6 +2781,14 @@ PySet_Add(PyObject *anyset, PyObject *key) return rv; } + if (PyFrozenSet_Check(anyset) || _PyObject_IsUniquelyReferenced(anyset)) { + // We can only change frozensets if they are uniquely referenced. The + // API limits the usage of `PySet_Add` to "fill in the values of brand + // new frozensets before they are exposed to other code". In this case, + // this can be done without a lock. + return set_add_key((PySetObject *)anyset, key); + } + PyErr_BadInternalCall(); return -1; } From 68ecfe52617d8ff43d46e8b84e9b82fda85b8c1e Mon Sep 17 00:00:00 2001 From: alperyoney Date: Thu, 23 Oct 2025 13:32:08 -0700 Subject: [PATCH 5/5] Fix the condition --- Objects/setobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Objects/setobject.c b/Objects/setobject.c index 556063619db39f..8f857af97d108a 100644 --- a/Objects/setobject.c +++ b/Objects/setobject.c @@ -2781,7 +2781,7 @@ PySet_Add(PyObject *anyset, PyObject *key) return rv; } - if (PyFrozenSet_Check(anyset) || _PyObject_IsUniquelyReferenced(anyset)) { + if (PyFrozenSet_Check(anyset) && _PyObject_IsUniquelyReferenced(anyset)) { // We can only change frozensets if they are uniquely referenced. The // API limits the usage of `PySet_Add` to "fill in the values of brand // new frozensets before they are exposed to other code". In this case,