https://github.com/python/cpython/commit/47029802a6feb532461dc9a0907d6123fe04429b
commit: 47029802a6feb532461dc9a0907d6123fe04429b
branch: main
author: Pieter Eendebak <[email protected]>
committer: kumaraditya303 <[email protected]>
date: 2026-09-12T09:09:44+05:30
summary:

gh-128213: fast path for bytes creation from list and tuple (#132590)

Co-authored-by: Ben Hsing <[email protected]>
Co-authored-by: Kumar Aditya <[email protected]>

files:
A Lib/test/test_free_threading/test_bytes_object.py
A 
Misc/NEWS.d/next/Core_and_Builtins/2024-12-24-08-44-49.gh-issue-128213.Y71jDi.rst
M Objects/bytesobject.c

diff --git a/Lib/test/test_free_threading/test_bytes_object.py 
b/Lib/test/test_free_threading/test_bytes_object.py
new file mode 100644
index 000000000000000..a371e3d533a2cb9
--- /dev/null
+++ b/Lib/test/test_free_threading/test_bytes_object.py
@@ -0,0 +1,37 @@
+import unittest
+from threading import Thread, Barrier
+from test.support import threading_helper
+
+threading_helper.requires_working_threading(module=True)
+
+
+class BytesThreading(unittest.TestCase):
+    @threading_helper.reap_threads
+    def test_conversion_from_mutating_list(self):
+        number_of_threads = 10
+        number_of_iterations = 10
+        barrier = Barrier(number_of_threads)
+
+        x = [1, 2, 3, 4, 5]
+        extends = [(ii,) * (2 + ii) for ii in range(number_of_threads)]
+
+        def work(ii):
+            barrier.wait()
+            for _ in range(100):
+                bytes(x)
+                x.extend(extends[ii])
+                if len(x) > 10:
+                    x[:] = [0]
+
+        for it in range(number_of_iterations):
+            worker_threads = []
+            for ii in range(number_of_threads):
+                worker_threads.append(Thread(target=work, args=[ii]))
+            with threading_helper.start_threads(worker_threads):
+                pass
+
+            barrier.reset()
+
+
+if __name__ == "__main__":
+    unittest.main()
diff --git 
a/Misc/NEWS.d/next/Core_and_Builtins/2024-12-24-08-44-49.gh-issue-128213.Y71jDi.rst
 
b/Misc/NEWS.d/next/Core_and_Builtins/2024-12-24-08-44-49.gh-issue-128213.Y71jDi.rst
new file mode 100644
index 000000000000000..85e3a7b2840fbc4
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2024-12-24-08-44-49.gh-issue-128213.Y71jDi.rst
@@ -0,0 +1,3 @@
+Speed up :class:`bytes` creation from :class:`list` and :class:`tuple` of 
integers.
+
+Patch by Ben Hsing and Pieter Eendebak
diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c
index 27ffc6e869ede3c..b84fdcd0ecc0153 100644
--- a/Objects/bytesobject.c
+++ b/Objects/bytesobject.c
@@ -6,6 +6,7 @@
 #include "pycore_bytesobject.h"   // _PyBytes_Find(), _PyBytes_RepeatBuffer()
 #include "pycore_call.h"          // _PyObject_CallNoArgs()
 #include "pycore_ceval.h"         // _PyEval_GetBuiltin()
+#include "pycore_critical_section.h" // 
Py_BEGIN_CRITICAL_SECTION_SEQUENCE_FAST()
 #include "pycore_format.h"        // F_LJUST
 #include "pycore_freelist.h"      // _Py_FREELIST_FREE()
 #include "pycore_global_objects.h"// _Py_GET_GLOBAL_OBJECT()
@@ -2985,82 +2986,39 @@ _PyBytes_FromBuffer(PyObject *x)
     return NULL;
 }
 
-static PyObject*
-_PyBytes_FromList(PyObject *x)
+/* Fast path for a list or tuple of ints.
+   Return 1 on success (*result set to the new bytes object),
+   0 to fall back to the slow path, or -1 on error (with an exception set). */
+static int
+_PyBytes_FromSequence_lock_held(PyObject *x, PyObject **result)
 {
-    Py_ssize_t size = PyList_GET_SIZE(x);
+    *result = NULL;
+    Py_ssize_t size = PySequence_Fast_GET_SIZE(x);
     PyBytesWriter *writer = PyBytesWriter_Create(size);
     if (writer == NULL) {
-        return NULL;
+        return -1;
     }
-    size = _PyBytesWriter_ResizeToAllocated(writer);
     char *str = PyBytesWriter_GetData(writer);
 
-    for (Py_ssize_t i = 0; i < PyList_GET_SIZE(x); i++) {
-        PyObject *item = _PyList_GetItemRef((PyListObject *)x, i);
-        if (item == NULL) {
-            goto error;
+    PyObject *const *items = PySequence_Fast_ITEMS(x);
+    for (Py_ssize_t i = 0; i < size; i++) {
+        Py_ssize_t value = PyLong_AsSsize_t(items[i]);
+        if (value == -1 && PyErr_Occurred()) {
+            PyBytesWriter_Discard(writer);
+            PyErr_Clear();
+            return 0;
         }
-        Py_ssize_t value = PyNumber_AsSsize_t(item, NULL);
-        Py_DECREF(item);
-        if (value == -1 && PyErr_Occurred())
-            goto error;
 
         if (value < 0 || value >= 256) {
             PyErr_SetString(PyExc_ValueError,
                             "bytes must be in range(0, 256)");
-            goto error;
-        }
-
-        if (i >= size) {
-            str = _PyBytesWriter_ResizeAndUpdatePointer(writer, size + 1, str);
-            if (str == NULL) {
-                goto error;
-            }
-
-            // Set the writer size to its allocated size
-            size = _PyBytesWriter_ResizeToAllocated(writer);
-        }
-        *str++ = (char) value;
-    }
-    return PyBytesWriter_FinishWithPointer(writer, str);
-
-error:
-    PyBytesWriter_Discard(writer);
-    return NULL;
-}
-
-static PyObject*
-_PyBytes_FromTuple(PyObject *x)
-{
-    Py_ssize_t i, size = PyTuple_GET_SIZE(x);
-    Py_ssize_t value;
-    PyObject *item;
-
-    PyBytesWriter *writer = PyBytesWriter_Create(size);
-    if (writer == NULL) {
-        return NULL;
-    }
-    char *str = PyBytesWriter_GetData(writer);
-
-    for (i = 0; i < size; i++) {
-        item = PyTuple_GET_ITEM(x, i);
-        value = PyNumber_AsSsize_t(item, NULL);
-        if (value == -1 && PyErr_Occurred())
-            goto error;
-
-        if (value < 0 || value >= 256) {
-            PyErr_SetString(PyExc_ValueError,
-                            "bytes must be in range(0, 256)");
-            goto error;
+            PyBytesWriter_Discard(writer);
+            return -1;
         }
         *str++ = (char) value;
     }
-    return PyBytesWriter_Finish(writer);
-
-  error:
-    PyBytesWriter_Discard(writer);
-    return NULL;
+    *result = PyBytesWriter_Finish(writer);
+    return *result != NULL ? 1 : -1;
 }
 
 static PyObject *
@@ -3143,11 +3101,15 @@ PyBytes_FromObject(PyObject *x)
     if (PyObject_CheckBuffer(x))
         return _PyBytes_FromBuffer(x);
 
-    if (PyList_CheckExact(x))
-        return _PyBytes_FromList(x);
-
-    if (PyTuple_CheckExact(x))
-        return _PyBytes_FromTuple(x);
+    if (PyList_CheckExact(x) || PyTuple_CheckExact(x)) {
+        int rc;
+        Py_BEGIN_CRITICAL_SECTION_SEQUENCE_FAST(x);
+        rc = _PyBytes_FromSequence_lock_held(x, &result);
+        Py_END_CRITICAL_SECTION_SEQUENCE_FAST();
+        if (rc != 0) {
+            return result;
+        }
+    }
 
     if (!PyUnicode_Check(x)) {
         it = PyObject_GetIter(x);

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to