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 00000000000000..a371e3d533a2cb --- /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 00000000000000..85e3a7b2840fbc --- /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 2ae55b33f4f49d..f4d303196fc34b 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);