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..e425258d3615aa --- /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. Benchmarks show that from a list with 1000000 random numbers the time to create a bytes object is reduced by around 31%, or 30% with 10000 numbers, or 27% with 100 numbers. + +Patch by Ben Hsing diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c index 533089d25cd73a..858aa3b6459540 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() @@ -2810,70 +2811,27 @@ _PyBytes_FromBuffer(PyObject *x) } static PyObject* -_PyBytes_FromList(PyObject *x) +_PyBytes_FromSequence(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; - } - - if (i >= size) { - str = _PyBytesWriter_Resize(&writer, str, size+1); - if (str == NULL) - return NULL; - size = writer.allocated; - } - *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()) + char *str = PyBytes_AS_STRING(bytes); + PyObject *const *items = PySequence_Fast_ITEMS(x); + Py_BEGIN_CRITICAL_SECTION_SEQUENCE_FAST(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 */ + bytes = Py_None; + goto done; + } + Py_ssize_t value = PyNumber_AsSsize_t(items[i], NULL); + if (value == -1 && PyErr_Occurred()) { goto error; - + } if (value < 0 || value >= 256) { PyErr_SetString(PyExc_ValueError, "bytes must be in range(0, 256)"); @@ -2881,11 +2839,18 @@ _PyBytes_FromTuple(PyObject *x) } *str++ = (char) value; } - return bytes; - + goto done; error: Py_DECREF(bytes); - return NULL; + bytes = NULL; + done: + /* some C parsers require a label not to be at the end of a compound + statement, which the ending macro of a critical section introduces, so + we need an empty statement here to satisfy that syntax rule */ + ; + /* both success and failure need to end the critical section */ + Py_END_CRITICAL_SECTION_SEQUENCE_FAST(); + return bytes; } static PyObject * @@ -2968,11 +2933,13 @@ 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)) { + PyObject *bytes = _PyBytes_FromSequence(x); + /* Py_None as a fallback sentinel to the slow path */ + if (bytes != Py_None) { + return bytes; + } + } if (!PyUnicode_Check(x)) { it = PyObject_GetIter(x);