Skip to content

Commit c73d897

Browse files
v0ropaevvstinner
authored andcommitted
gh-158585: Optimize PyBytesWriter_FinishWithSize()
Add specialized bytes_resize_inplace() and inline PyBytesWriter_Discard() in PyBytesWriter_FinishWithSize(). Add also bytes_get_char() function.
1 parent ab01d18 commit c73d897

1 file changed

Lines changed: 83 additions & 60 deletions

File tree

‎Objects/bytesobject.c‎

Lines changed: 83 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,51 @@ _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+
assert(_PyObject_IsUniquelyReferenced(v));
3362+
assert(PyBytes_GET_SIZE(v) >= 1);
3363+
assert(newsize >= 1);
3364+
3365+
// Only mutable bytes can be resized in-place
3366+
assert(_PyBytes_IsMutable(v));
3367+
3368+
if ((size_t)newsize > (size_t)PY_SSIZE_T_MAX - PyBytesObject_SIZE) {
3369+
PyErr_SetString(PyExc_OverflowError,
3370+
"byte string is too large");
3371+
return -1;
3372+
}
3373+
3374+
#ifdef Py_TRACE_REFS
3375+
_Py_ForgetReference(v);
3376+
#endif
3377+
_PyReftracerTrack(v, PyRefTracer_DESTROY);
3378+
3379+
PyObject *result = PyObject_Realloc(v, PyBytesObject_SIZE + newsize);
3380+
if (result == NULL) {
3381+
#ifdef Py_TRACE_REFS
3382+
_Py_AddToAllObjects(v);
3383+
#endif
3384+
_PyReftracerTrack(v, PyRefTracer_CREATE);
3385+
3386+
PyErr_NoMemory();
3387+
return -1;
3388+
}
3389+
3390+
*pv = result;
3391+
v = result;
3392+
_Py_NewReferenceNoTotal(v);
3393+
PyBytesObject *sv = (PyBytesObject *)v;
3394+
Py_SET_SIZE(sv, newsize);
3395+
sv->ob_sval[newsize] = '\0';
3396+
set_ob_shash(sv, -1); /* invalidate cached hash value */
3397+
assert(_PyBytes_IsMutable(*pv));
3398+
return 0;
3399+
}
3400+
3401+
33503402
/* The following function breaks the notion that bytes are immutable:
33513403
it changes the size of a bytes object. You can think of it
33523404
as creating a new bytes object and destroying the old one, only
@@ -3410,40 +3462,7 @@ _PyBytes_ResizeKeepOnError(PyObject **pv, Py_ssize_t newsize)
34103462
return 0;
34113463
}
34123464

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;
3465+
return bytes_resize_inplace(pv, newsize);
34473466
}
34483467

34493468

@@ -3903,9 +3922,6 @@ PyBytesWriter_FinishWithSize(PyBytesWriter *writer, Py_ssize_t size)
39033922
{
39043923
assert(byteswriter_check_consistency(writer));
39053924

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.
39093925
if (size < 0) {
39103926
PyErr_Format(PyExc_ValueError, "size must be positive");
39113927
goto error;
@@ -3919,6 +3935,13 @@ PyBytesWriter_FinishWithSize(PyBytesWriter *writer, Py_ssize_t size)
39193935
PyObject *result;
39203936
if (size == 0 && !writer->use_bytearray) {
39213937
result = bytes_get_empty();
3938+
if (writer->obj != NULL) {
3939+
#ifdef Py_DEBUG
3940+
byteswriter_reset_trailing_byte(writer);
3941+
#endif
3942+
Py_DECREF(writer->obj);
3943+
writer->obj = NULL;
3944+
}
39223945
}
39233946
else if (writer->obj != NULL) {
39243947
// Truncate the bytes/bytearray object if needed
@@ -3935,14 +3958,11 @@ PyBytesWriter_FinishWithSize(PyBytesWriter *writer, Py_ssize_t size)
39353958
}
39363959
else {
39373960
if (size == 1) {
3938-
// Get the single byte singleton
39393961
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);
3962+
Py_SETREF(writer->obj, bytes_get_char(ch));
39433963
}
39443964
else if (size != PyBytes_GET_SIZE(writer->obj)) {
3945-
if (_PyBytes_Resize(&writer->obj, size)) {
3965+
if (bytes_resize_inplace(&writer->obj, size)) {
39463966
goto error;
39473967
}
39483968
}
@@ -3953,23 +3973,26 @@ PyBytesWriter_FinishWithSize(PyBytesWriter *writer, Py_ssize_t size)
39533973
}
39543974
else {
39553975
// Create an object from the small buffer
3976+
const char *buffer = (const char *)writer->small_buffer;
39563977
if (writer->use_bytearray) {
3957-
result = PyByteArray_FromStringAndSize(writer->small_buffer, size);
3978+
result = PyByteArray_FromStringAndSize(buffer, size);
39583979
}
39593980
else {
3960-
// The function returns single byte singleton if size equals 1
3961-
result = PyBytes_FromStringAndSize(writer->small_buffer, size);
3981+
if (size == 1) {
3982+
result = bytes_get_char((uint8_t)buffer[0]);
3983+
}
3984+
else {
3985+
result = _PyBytes_FromSize(size, 0);
3986+
if (result == NULL) {
3987+
goto error;
3988+
}
3989+
memcpy(PyBytes_AS_STRING(result), buffer, size);
3990+
}
39623991
}
39633992
}
39643993

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);
3994+
assert(writer->obj == NULL);
3995+
_Py_FREELIST_FREE(bytes_writers, writer, PyMem_Free);
39733996
return result;
39743997

39753998
error:

0 commit comments

Comments
 (0)