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 */
 };
 

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.

1 participant