Skip to content

Conversation

@vstinner
Copy link
Member

@vstinner vstinner commented Oct 14, 2025

  • Add _PyTuple_NewNoTrack() and _PyTuple_ResizeNoTrack() helper functions.
  • Modify PySequence_Tuple() to use PyTupleWriter API.
  • Soft deprecate _PyTuple_Resize().

📚 Documentation preview 📚: https://cpython-previews--140129.org.readthedocs.build/

* Add _PyTuple_NewNoTrack() and _PyTuple_ResizeNoTrack() helper
  functions.
* Modify PySequence_Tuple() to use PyTupleWriter API.
* Soft deprecate _PyTuple_Resize().
@vstinner
Copy link
Member Author

Benchmark based on my previous benchmark.

Compare PyTuple_SET_ITEM() to PyTupleWriter_AddSteal() where heap is PR gh-139891 and stack is this PR (gh-140129).

Benchmark tuple heap stack
tuple-1 31.3 ns 42.3 ns: 1.35x slower 39.1 ns: 1.25x slower
tuple-5 51.0 ns 70.4 ns: 1.38x slower 65.2 ns: 1.28x slower
tuple-10 74.6 ns 106 ns: 1.41x slower 96.7 ns: 1.30x slower
tuple-100 567 ns 803 ns: 1.42x slower 754 ns: 1.33x slower
tuple-1000 5.52 us 7.73 us: 1.40x slower 7.33 us: 1.33x slower
Geometric mean (ref) 1.39x slower 1.30x slower
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index 4e73be20e1b..2a1d0b85a23 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -2562,6 +2562,74 @@ toggle_reftrace_printer(PyObject *ob, PyObject *arg)
     Py_RETURN_NONE;
 }
 
+static PyObject *
+bench_tuple(PyObject *ob, PyObject *args)
+{
+    Py_ssize_t size, loops;
+    if (!PyArg_ParseTuple(args, "nn", &size, &loops)) {
+        return NULL;
+    }
+
+    PyTime_t t1, t2;
+    PyTime_PerfCounterRaw(&t1);
+    for (Py_ssize_t i=0; i < loops; i++) {
+        PyObject *tuple = PyTuple_New(size);
+        if (tuple == NULL) {
+            return NULL;
+        }
+
+        for (int i=0; i < size; i++) {
+            PyObject *item = PyLong_FromLong(i);
+            if (item == NULL) {
+                Py_DECREF(tuple);
+                return NULL;
+            }
+            PyTuple_SET_ITEM(tuple, i, item);
+        }
+
+        Py_DECREF(tuple);
+    }
+    PyTime_PerfCounterRaw(&t2);
+    return PyFloat_FromDouble(PyTime_AsSecondsDouble(t2 - t1));
+}
+
+static PyObject *
+bench_writer(PyObject *ob, PyObject *args)
+{
+    Py_ssize_t size, loops;
+    if (!PyArg_ParseTuple(args, "nn", &size, &loops)) {
+        return NULL;
+    }
+
+    PyTime_t t1, t2;
+    PyTime_PerfCounterRaw(&t1);
+    for (Py_ssize_t i=0; i < loops; i++) {
+        PyTupleWriter writer;
+        if (PyTupleWriter_Init(&writer, size) < 0) {
+            return NULL;
+        }
+
+        for (int i=0; i < size; i++) {
+            PyObject *item = PyLong_FromLong(i);
+            if (item == NULL) {
+                return NULL;
+            }
+            if (PyTupleWriter_AddSteal(&writer, item) < 0) {
+                PyTupleWriter_Discard(&writer);
+                return NULL;
+            }
+        }
+
+        PyObject *tuple = PyTupleWriter_Finish(&writer);
+        if (tuple == NULL) {
+            return NULL;
+        }
+        Py_DECREF(tuple);
+    }
+    PyTime_PerfCounterRaw(&t2);
+    return PyFloat_FromDouble(PyTime_AsSecondsDouble(t2 - t1));
+}
+
 static PyMethodDef TestMethods[] = {
     {"set_errno",               set_errno,                       METH_VARARGS},
     {"test_config",             test_config,                     METH_NOARGS},
@@ -2656,6 +2724,8 @@ static PyMethodDef TestMethods[] = {
     {"test_atexit", test_atexit, METH_NOARGS},
     {"code_offset_to_line", _PyCFunction_CAST(code_offset_to_line), METH_FASTCALL},
     {"toggle_reftrace_printer", toggle_reftrace_printer, METH_O},
+    {"bench_tuple", bench_tuple, METH_VARARGS},
+    {"bench_writer", bench_writer, METH_VARARGS},
     {NULL, NULL} /* sentinel */
 };
 

Co-authored-by: Maurycy Pawłowski-Wieroński <[email protected]>
@vstinner
Copy link
Member Author

vstinner commented Dec 2, 2025

I wrote this API to get rid of _PyTuple_Resize() which treats an immutable tuple as mutable. I would like to avoid that: do not provide any function to mutate an immutable tuple.

I failed to convince other core developers that this PyTupleWriter API is useful. Moreover, it's 1.4x slower than the current API based on macros (which mutate an immutable tuple!).

I prefer to give up on PyTupleWriter. It seems like _PyTuple_Resize() has to stay for a few more years. In the meanwhile, one option is to create a list and then call PyList_AsTuple() to convert it to a tuple.

@vstinner vstinner closed this Dec 2, 2025
@vstinner vstinner deleted the tuplewriter_stack branch December 2, 2025 12:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants