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..ee3bd42f61c175 --- /dev/null +++ b/Lib/test/test_free_threading/test_bytes_object.py @@ -0,0 +1,37 @@ +import unittest +import sys +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_list(self): + number_of_threads = 10 + number_of_iterations = 10 + barrier = Barrier(number_of_threads) + + x = [1, 2, 3, 4, 5] + e = [ (ii,)*(2+4*ii) for ii in range(number_of_threads)] # range of sizes to extend + def work(ii): + barrier.wait() + for _ in range(1000): + bytes(x) + x.extend(e[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..ca47f0cfc3522a --- /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 by 27-31%. + +Patch by Ben Hsing and Pieter Eendebak diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c index 87ea1162e03513..e5cb484ee40b30 100644 --- a/Objects/bytesobject.c +++ b/Objects/bytesobject.c @@ -6,6 +6,7 @@ #include "pycore_bytesobject.h" // _PyBytes_Find(), _PyBytes_Repeat() #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_global_objects.h"// _Py_GET_GLOBAL_OBJECT() #include "pycore_initconfig.h" // _PyStatus_OK() @@ -2859,82 +2860,35 @@ _PyBytes_FromBuffer(PyObject *x) } static PyObject* -_PyBytes_FromList(PyObject *x) +_PyBytes_FromSequence_lock_held(PyObject *x) { - Py_ssize_t i, size = PyList_GET_SIZE(x); - Py_ssize_t value; - char *str; - PyObject *item; - _PyBytesWriter writer; - - _PyBytesWriter_Init(&writer); - str = _PyBytesWriter_Alloc(&writer, size); - if (str == NULL) + Py_ssize_t size = PySequence_Fast_GET_SIZE(x); + PyObject *bytes = _PyBytes_FromSize(size, 0); + if (bytes == NULL) { return NULL; - writer.overallocate = 1; - size = writer.allocated; - - for (i = 0; i < PyList_GET_SIZE(x); i++) { - item = PyList_GET_ITEM(x, i); - Py_INCREF(item); - 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; + } + char *str = PyBytes_AS_STRING(bytes); + PyObject *const *items = PySequence_Fast_ITEMS(x); + for (Py_ssize_t i = 0; i < size; i++) { + if (!PyLong_Check(items[i])) { + Py_DECREF(bytes); + /* Py_None as a fallback sentinel to the slow path */ + Py_RETURN_NONE; } - - if (i >= size) { - str = _PyBytesWriter_Resize(&writer, str, size+1); - if (str == NULL) - return NULL; - size = writer.allocated; + Py_ssize_t value = PyNumber_AsSsize_t(items[i], NULL); + if (value == -1 && PyErr_Occurred()) { + Py_DECREF(bytes); + return NULL; } - *str++ = (char) value; - } - return _PyBytesWriter_Finish(&writer, str); - - error: - _PyBytesWriter_Dealloc(&writer); - return NULL; -} - -static PyObject* -_PyBytes_FromTuple(PyObject *x) -{ - PyObject *bytes; - Py_ssize_t i, size = PyTuple_GET_SIZE(x); - Py_ssize_t value; - char *str; - PyObject *item; - - bytes = PyBytes_FromStringAndSize(NULL, size); - if (bytes == NULL) - return NULL; - str = ((PyBytesObject *)bytes)->ob_sval; - - 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; + Py_DECREF(bytes); + return NULL; } *str++ = (char) value; } return bytes; - - error: - Py_DECREF(bytes); - return NULL; } static PyObject * @@ -3017,11 +2971,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)) { + Py_BEGIN_CRITICAL_SECTION_SEQUENCE_FAST(x); + result = _PyBytes_FromSequence_lock_held(x); + Py_END_CRITICAL_SECTION_SEQUENCE_FAST(); + /* Py_None as a fallback sentinel to the slow path */ + if (result != Py_None) { + return result; + } + } if (!PyUnicode_Check(x)) { it = PyObject_GetIter(x);