Skip to content

Commit 4702980

Browse files
eendebakptblhsingkumaraditya303
authored
gh-128213: fast path for bytes creation from list and tuple (#132590)
Co-authored-by: Ben Hsing <blhsing@gmail.com> Co-authored-by: Kumar Aditya <kumaraditya@python.org>
1 parent 051b168 commit 4702980

3 files changed

Lines changed: 69 additions & 67 deletions

File tree

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import unittest
2+
from threading import Thread, Barrier
3+
from test.support import threading_helper
4+
5+
threading_helper.requires_working_threading(module=True)
6+
7+
8+
class BytesThreading(unittest.TestCase):
9+
@threading_helper.reap_threads
10+
def test_conversion_from_mutating_list(self):
11+
number_of_threads = 10
12+
number_of_iterations = 10
13+
barrier = Barrier(number_of_threads)
14+
15+
x = [1, 2, 3, 4, 5]
16+
extends = [(ii,) * (2 + ii) for ii in range(number_of_threads)]
17+
18+
def work(ii):
19+
barrier.wait()
20+
for _ in range(100):
21+
bytes(x)
22+
x.extend(extends[ii])
23+
if len(x) > 10:
24+
x[:] = [0]
25+
26+
for it in range(number_of_iterations):
27+
worker_threads = []
28+
for ii in range(number_of_threads):
29+
worker_threads.append(Thread(target=work, args=[ii]))
30+
with threading_helper.start_threads(worker_threads):
31+
pass
32+
33+
barrier.reset()
34+
35+
36+
if __name__ == "__main__":
37+
unittest.main()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Speed up :class:`bytes` creation from :class:`list` and :class:`tuple` of integers.
2+
3+
Patch by Ben Hsing and Pieter Eendebak

Objects/bytesobject.c

Lines changed: 29 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "pycore_bytesobject.h" // _PyBytes_Find(), _PyBytes_RepeatBuffer()
77
#include "pycore_call.h" // _PyObject_CallNoArgs()
88
#include "pycore_ceval.h" // _PyEval_GetBuiltin()
9+
#include "pycore_critical_section.h" // Py_BEGIN_CRITICAL_SECTION_SEQUENCE_FAST()
910
#include "pycore_format.h" // F_LJUST
1011
#include "pycore_freelist.h" // _Py_FREELIST_FREE()
1112
#include "pycore_global_objects.h"// _Py_GET_GLOBAL_OBJECT()
@@ -2985,82 +2986,39 @@ _PyBytes_FromBuffer(PyObject *x)
29852986
return NULL;
29862987
}
29872988

2988-
static PyObject*
2989-
_PyBytes_FromList(PyObject *x)
2989+
/* Fast path for a list or tuple of ints.
2990+
Return 1 on success (*result set to the new bytes object),
2991+
0 to fall back to the slow path, or -1 on error (with an exception set). */
2992+
static int
2993+
_PyBytes_FromSequence_lock_held(PyObject *x, PyObject **result)
29902994
{
2991-
Py_ssize_t size = PyList_GET_SIZE(x);
2995+
*result = NULL;
2996+
Py_ssize_t size = PySequence_Fast_GET_SIZE(x);
29922997
PyBytesWriter *writer = PyBytesWriter_Create(size);
29932998
if (writer == NULL) {
2994-
return NULL;
2999+
return -1;
29953000
}
2996-
size = _PyBytesWriter_ResizeToAllocated(writer);
29973001
char *str = PyBytesWriter_GetData(writer);
29983002

2999-
for (Py_ssize_t i = 0; i < PyList_GET_SIZE(x); i++) {
3000-
PyObject *item = _PyList_GetItemRef((PyListObject *)x, i);
3001-
if (item == NULL) {
3002-
goto error;
3003+
PyObject *const *items = PySequence_Fast_ITEMS(x);
3004+
for (Py_ssize_t i = 0; i < size; i++) {
3005+
Py_ssize_t value = PyLong_AsSsize_t(items[i]);
3006+
if (value == -1 && PyErr_Occurred()) {
3007+
PyBytesWriter_Discard(writer);
3008+
PyErr_Clear();
3009+
return 0;
30033010
}
3004-
Py_ssize_t value = PyNumber_AsSsize_t(item, NULL);
3005-
Py_DECREF(item);
3006-
if (value == -1 && PyErr_Occurred())
3007-
goto error;
30083011

30093012
if (value < 0 || value >= 256) {
30103013
PyErr_SetString(PyExc_ValueError,
30113014
"bytes must be in range(0, 256)");
3012-
goto error;
3013-
}
3014-
3015-
if (i >= size) {
3016-
str = _PyBytesWriter_ResizeAndUpdatePointer(writer, size + 1, str);
3017-
if (str == NULL) {
3018-
goto error;
3019-
}
3020-
3021-
// Set the writer size to its allocated size
3022-
size = _PyBytesWriter_ResizeToAllocated(writer);
3023-
}
3024-
*str++ = (char) value;
3025-
}
3026-
return PyBytesWriter_FinishWithPointer(writer, str);
3027-
3028-
error:
3029-
PyBytesWriter_Discard(writer);
3030-
return NULL;
3031-
}
3032-
3033-
static PyObject*
3034-
_PyBytes_FromTuple(PyObject *x)
3035-
{
3036-
Py_ssize_t i, size = PyTuple_GET_SIZE(x);
3037-
Py_ssize_t value;
3038-
PyObject *item;
3039-
3040-
PyBytesWriter *writer = PyBytesWriter_Create(size);
3041-
if (writer == NULL) {
3042-
return NULL;
3043-
}
3044-
char *str = PyBytesWriter_GetData(writer);
3045-
3046-
for (i = 0; i < size; i++) {
3047-
item = PyTuple_GET_ITEM(x, i);
3048-
value = PyNumber_AsSsize_t(item, NULL);
3049-
if (value == -1 && PyErr_Occurred())
3050-
goto error;
3051-
3052-
if (value < 0 || value >= 256) {
3053-
PyErr_SetString(PyExc_ValueError,
3054-
"bytes must be in range(0, 256)");
3055-
goto error;
3015+
PyBytesWriter_Discard(writer);
3016+
return -1;
30563017
}
30573018
*str++ = (char) value;
30583019
}
3059-
return PyBytesWriter_Finish(writer);
3060-
3061-
error:
3062-
PyBytesWriter_Discard(writer);
3063-
return NULL;
3020+
*result = PyBytesWriter_Finish(writer);
3021+
return *result != NULL ? 1 : -1;
30643022
}
30653023

30663024
static PyObject *
@@ -3143,11 +3101,15 @@ PyBytes_FromObject(PyObject *x)
31433101
if (PyObject_CheckBuffer(x))
31443102
return _PyBytes_FromBuffer(x);
31453103

3146-
if (PyList_CheckExact(x))
3147-
return _PyBytes_FromList(x);
3148-
3149-
if (PyTuple_CheckExact(x))
3150-
return _PyBytes_FromTuple(x);
3104+
if (PyList_CheckExact(x) || PyTuple_CheckExact(x)) {
3105+
int rc;
3106+
Py_BEGIN_CRITICAL_SECTION_SEQUENCE_FAST(x);
3107+
rc = _PyBytes_FromSequence_lock_held(x, &result);
3108+
Py_END_CRITICAL_SECTION_SEQUENCE_FAST();
3109+
if (rc != 0) {
3110+
return result;
3111+
}
3112+
}
31513113

31523114
if (!PyUnicode_Check(x)) {
31533115
it = PyObject_GetIter(x);

0 commit comments

Comments
 (0)