Skip to content

Commit ee782e6

Browse files
authored
gh-158585: Optimize PyBytesWriter_FinishWithSize() (#158586)
Add specialized bytes_resize_inplace() and inline PyBytesWriter_Discard() in PyBytesWriter_FinishWithSize(). No longer copy the buffer if _PyObject_IsUniquelyReferenced() is false. Add also bytes_get_char() function.
1 parent a4f28a5 commit ee782e6

1 file changed

Lines changed: 85 additions & 60 deletions

File tree

‎Objects/bytesobject.c‎

Lines changed: 85 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,25 @@ static Py_ssize_t _PyBytesWriter_ResizeToAllocated(PyBytesWriter *writer);
4242

4343

4444
// Return a reference to the immortal empty bytes string singleton.
45-
static inline PyObject* bytes_get_empty(void)
45+
static inline PyObject*
46+
bytes_get_empty(void)
4647
{
4748
PyObject *empty = &EMPTY->ob_base.ob_base;
4849
assert(_Py_IsImmortal(empty));
4950
return empty;
5051
}
5152

5253

54+
// The function cannot fail
55+
static inline PyObject*
56+
bytes_get_char(uint8_t ch)
57+
{
58+
PyObject *obj = (PyObject*)CHARACTER(ch);
59+
assert(_Py_IsImmortal(obj));
60+
return obj;
61+
}
62+
63+
5364
static inline void
5465
set_ob_shash(PyBytesObject *a, Py_hash_t hash)
5566
{
@@ -130,9 +141,7 @@ PyBytes_FromStringAndSize(const char *str, Py_ssize_t size)
130141
return NULL;
131142
}
132143
if (size == 1 && str != NULL) {
133-
op = CHARACTER(*str & 255);
134-
assert(_Py_IsImmortal(op));
135-
return (PyObject *)op;
144+
return bytes_get_char((uint8_t)*str);
136145
}
137146
if (size == 0) {
138147
return bytes_get_empty();
@@ -166,9 +175,7 @@ PyBytes_FromString(const char *str)
166175
return bytes_get_empty();
167176
}
168177
else if (size == 1) {
169-
op = CHARACTER(*str & 255);
170-
assert(_Py_IsImmortal(op));
171-
return (PyObject *)op;
178+
return bytes_get_char((uint8_t)*str);
172179
}
173180

174181
/* Inline PyObject_NewVar */
@@ -3347,6 +3354,53 @@ _PyBytes_IsMutable(PyObject *self)
33473354
#endif
33483355

33493356

3357+
static inline int
3358+
bytes_resize_inplace(PyObject **pv, Py_ssize_t newsize)
3359+
{
3360+
PyObject *v = *pv;
3361+
// Do not test _PyObject_IsUniquelyReferenced(). The function is used by
3362+
// PyBytesWriter_FinishWithSize() and its caller can have its own lock.
3363+
assert(Py_REFCNT(v) == 1);
3364+
assert(PyBytes_GET_SIZE(v) >= 1);
3365+
assert(newsize >= 1);
3366+
3367+
// Only mutable bytes can be resized in-place
3368+
assert(_PyBytes_IsMutable(v));
3369+
3370+
if ((size_t)newsize > (size_t)PY_SSIZE_T_MAX - PyBytesObject_SIZE) {
3371+
PyErr_SetString(PyExc_OverflowError,
3372+
"byte string is too large");
3373+
return -1;
3374+
}
3375+
3376+
#ifdef Py_TRACE_REFS
3377+
_Py_ForgetReference(v);
3378+
#endif
3379+
_PyReftracerTrack(v, PyRefTracer_DESTROY);
3380+
3381+
PyObject *result = PyObject_Realloc(v, PyBytesObject_SIZE + newsize);
3382+
if (result == NULL) {
3383+
#ifdef Py_TRACE_REFS
3384+
_Py_AddToAllObjects(v);
3385+
#endif
3386+
_PyReftracerTrack(v, PyRefTracer_CREATE);
3387+
3388+
PyErr_NoMemory();
3389+
return -1;
3390+
}
3391+
3392+
*pv = result;
3393+
v = result;
3394+
_Py_NewReferenceNoTotal(v);
3395+
PyBytesObject *sv = (PyBytesObject *)v;
3396+
Py_SET_SIZE(sv, newsize);
3397+
sv->ob_sval[newsize] = '\0';
3398+
set_ob_shash(sv, -1); /* invalidate cached hash value */
3399+
assert(_PyBytes_IsMutable(*pv));
3400+
return 0;
3401+
}
3402+
3403+
33503404
/* The following function breaks the notion that bytes are immutable:
33513405
it changes the size of a bytes object. You can think of it
33523406
as creating a new bytes object and destroying the old one, only
@@ -3410,40 +3464,7 @@ _PyBytes_ResizeKeepOnError(PyObject **pv, Py_ssize_t newsize)
34103464
return 0;
34113465
}
34123466

3413-
// Only mutable bytes can be resized in-place
3414-
assert(_PyBytes_IsMutable(v));
3415-
3416-
if ((size_t)newsize > (size_t)PY_SSIZE_T_MAX - PyBytesObject_SIZE) {
3417-
PyErr_SetString(PyExc_OverflowError,
3418-
"byte string is too large");
3419-
return -1;
3420-
}
3421-
3422-
#ifdef Py_TRACE_REFS
3423-
_Py_ForgetReference(v);
3424-
#endif
3425-
_PyReftracerTrack(v, PyRefTracer_DESTROY);
3426-
3427-
result = (PyObject *)PyObject_Realloc(v, PyBytesObject_SIZE + newsize);
3428-
if (result == NULL) {
3429-
#ifdef Py_TRACE_REFS
3430-
_Py_AddToAllObjects(v);
3431-
#endif
3432-
_PyReftracerTrack(v, PyRefTracer_CREATE);
3433-
3434-
PyErr_NoMemory();
3435-
return -1;
3436-
}
3437-
3438-
*pv = result;
3439-
v = result;
3440-
_Py_NewReferenceNoTotal(v);
3441-
PyBytesObject *sv = (PyBytesObject *)v;
3442-
Py_SET_SIZE(sv, newsize);
3443-
sv->ob_sval[newsize] = '\0';
3444-
set_ob_shash(sv, -1); /* invalidate cached hash value */
3445-
assert(_PyBytes_IsMutable(*pv));
3446-
return 0;
3467+
return bytes_resize_inplace(pv, newsize);
34473468
}
34483469

34493470

@@ -3903,9 +3924,6 @@ PyBytesWriter_FinishWithSize(PyBytesWriter *writer, Py_ssize_t size)
39033924
{
39043925
assert(byteswriter_check_consistency(writer));
39053926

3906-
// Check for negative size here to raise ValueError in all cases, rather
3907-
// than having a different exception depending on the code path. For
3908-
// example, _PyBytes_Resize() raises SystemError on negative size.
39093927
if (size < 0) {
39103928
PyErr_Format(PyExc_ValueError, "size must be positive");
39113929
goto error;
@@ -3919,6 +3937,13 @@ PyBytesWriter_FinishWithSize(PyBytesWriter *writer, Py_ssize_t size)
39193937
PyObject *result;
39203938
if (size == 0 && !writer->use_bytearray) {
39213939
result = bytes_get_empty();
3940+
if (writer->obj != NULL) {
3941+
#ifdef Py_DEBUG
3942+
byteswriter_reset_trailing_byte(writer);
3943+
#endif
3944+
Py_DECREF(writer->obj);
3945+
writer->obj = NULL;
3946+
}
39223947
}
39233948
else if (writer->obj != NULL) {
39243949
// Truncate the bytes/bytearray object if needed
@@ -3935,14 +3960,11 @@ PyBytesWriter_FinishWithSize(PyBytesWriter *writer, Py_ssize_t size)
39353960
}
39363961
else {
39373962
if (size == 1) {
3938-
// Get the single byte singleton
39393963
unsigned char ch = PyBytes_AS_STRING(writer->obj)[0];
3940-
PyObject *op = (PyObject*)CHARACTER(ch);
3941-
assert(_Py_IsImmortal(op));
3942-
Py_SETREF(writer->obj, op);
3964+
Py_SETREF(writer->obj, bytes_get_char(ch));
39433965
}
39443966
else if (size != PyBytes_GET_SIZE(writer->obj)) {
3945-
if (_PyBytes_Resize(&writer->obj, size)) {
3967+
if (bytes_resize_inplace(&writer->obj, size)) {
39463968
goto error;
39473969
}
39483970
}
@@ -3953,23 +3975,26 @@ PyBytesWriter_FinishWithSize(PyBytesWriter *writer, Py_ssize_t size)
39533975
}
39543976
else {
39553977
// Create an object from the small buffer
3978+
const char *buffer = (const char *)writer->small_buffer;
39563979
if (writer->use_bytearray) {
3957-
result = PyByteArray_FromStringAndSize(writer->small_buffer, size);
3980+
result = PyByteArray_FromStringAndSize(buffer, size);
39583981
}
39593982
else {
3960-
// The function returns single byte singleton if size equals 1
3961-
result = PyBytes_FromStringAndSize(writer->small_buffer, size);
3983+
if (size == 1) {
3984+
result = bytes_get_char((uint8_t)buffer[0]);
3985+
}
3986+
else {
3987+
result = _PyBytes_FromSize(size, 0);
3988+
if (result == NULL) {
3989+
goto error;
3990+
}
3991+
memcpy(PyBytes_AS_STRING(result), buffer, size);
3992+
}
39623993
}
39633994
}
39643995

3965-
#ifdef Py_DEBUG
3966-
// Reset the writer, so byteswriter_check_consistency() doesn't fail
3967-
// in PyBytesWriter_Discard().
3968-
writer->size = 0;
3969-
byteswriter_write_canary_byte(writer);
3970-
#endif
3971-
3972-
PyBytesWriter_Discard(writer);
3996+
assert(writer->obj == NULL);
3997+
_Py_FREELIST_FREE(bytes_writers, writer, PyMem_Free);
39733998
return result;
39743999

39754000
error:

0 commit comments

Comments
 (0)