https://github.com/python/cpython/commit/ee782e6143b3fda2e7feb1216ff23860ad39b234
commit: ee782e6143b3fda2e7feb1216ff23860ad39b234
branch: main
author: Victor Stinner <[email protected]>
committer: vstinner <[email protected]>
date: 2026-10-02T03:06:58Z
summary:
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.
files:
M Objects/bytesobject.c
diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c
index 7e677108983f0fd..cb2c2f7bfae634f 100644
--- a/Objects/bytesobject.c
+++ b/Objects/bytesobject.c
@@ -42,7 +42,8 @@ static Py_ssize_t
_PyBytesWriter_ResizeToAllocated(PyBytesWriter *writer);
// Return a reference to the immortal empty bytes string singleton.
-static inline PyObject* bytes_get_empty(void)
+static inline PyObject*
+bytes_get_empty(void)
{
PyObject *empty = &EMPTY->ob_base.ob_base;
assert(_Py_IsImmortal(empty));
@@ -50,6 +51,16 @@ static inline PyObject* bytes_get_empty(void)
}
+// The function cannot fail
+static inline PyObject*
+bytes_get_char(uint8_t ch)
+{
+ PyObject *obj = (PyObject*)CHARACTER(ch);
+ assert(_Py_IsImmortal(obj));
+ return obj;
+}
+
+
static inline void
set_ob_shash(PyBytesObject *a, Py_hash_t hash)
{
@@ -130,9 +141,7 @@ PyBytes_FromStringAndSize(const char *str, Py_ssize_t size)
return NULL;
}
if (size == 1 && str != NULL) {
- op = CHARACTER(*str & 255);
- assert(_Py_IsImmortal(op));
- return (PyObject *)op;
+ return bytes_get_char((uint8_t)*str);
}
if (size == 0) {
return bytes_get_empty();
@@ -166,9 +175,7 @@ PyBytes_FromString(const char *str)
return bytes_get_empty();
}
else if (size == 1) {
- op = CHARACTER(*str & 255);
- assert(_Py_IsImmortal(op));
- return (PyObject *)op;
+ return bytes_get_char((uint8_t)*str);
}
/* Inline PyObject_NewVar */
@@ -3347,6 +3354,53 @@ _PyBytes_IsMutable(PyObject *self)
#endif
+static inline int
+bytes_resize_inplace(PyObject **pv, Py_ssize_t newsize)
+{
+ PyObject *v = *pv;
+ // Do not test _PyObject_IsUniquelyReferenced(). The function is used by
+ // PyBytesWriter_FinishWithSize() and its caller can have its own lock.
+ assert(Py_REFCNT(v) == 1);
+ assert(PyBytes_GET_SIZE(v) >= 1);
+ assert(newsize >= 1);
+
+ // Only mutable bytes can be resized in-place
+ assert(_PyBytes_IsMutable(v));
+
+ if ((size_t)newsize > (size_t)PY_SSIZE_T_MAX - PyBytesObject_SIZE) {
+ PyErr_SetString(PyExc_OverflowError,
+ "byte string is too large");
+ return -1;
+ }
+
+#ifdef Py_TRACE_REFS
+ _Py_ForgetReference(v);
+#endif
+ _PyReftracerTrack(v, PyRefTracer_DESTROY);
+
+ PyObject *result = PyObject_Realloc(v, PyBytesObject_SIZE + newsize);
+ if (result == NULL) {
+#ifdef Py_TRACE_REFS
+ _Py_AddToAllObjects(v);
+#endif
+ _PyReftracerTrack(v, PyRefTracer_CREATE);
+
+ PyErr_NoMemory();
+ return -1;
+ }
+
+ *pv = result;
+ v = result;
+ _Py_NewReferenceNoTotal(v);
+ PyBytesObject *sv = (PyBytesObject *)v;
+ Py_SET_SIZE(sv, newsize);
+ sv->ob_sval[newsize] = '\0';
+ set_ob_shash(sv, -1); /* invalidate cached hash value */
+ assert(_PyBytes_IsMutable(*pv));
+ return 0;
+}
+
+
/* The following function breaks the notion that bytes are immutable:
it changes the size of a bytes object. You can think of it
as creating a new bytes object and destroying the old one, only
@@ -3410,40 +3464,7 @@ _PyBytes_ResizeKeepOnError(PyObject **pv, Py_ssize_t
newsize)
return 0;
}
- // Only mutable bytes can be resized in-place
- assert(_PyBytes_IsMutable(v));
-
- if ((size_t)newsize > (size_t)PY_SSIZE_T_MAX - PyBytesObject_SIZE) {
- PyErr_SetString(PyExc_OverflowError,
- "byte string is too large");
- return -1;
- }
-
-#ifdef Py_TRACE_REFS
- _Py_ForgetReference(v);
-#endif
- _PyReftracerTrack(v, PyRefTracer_DESTROY);
-
- result = (PyObject *)PyObject_Realloc(v, PyBytesObject_SIZE + newsize);
- if (result == NULL) {
-#ifdef Py_TRACE_REFS
- _Py_AddToAllObjects(v);
-#endif
- _PyReftracerTrack(v, PyRefTracer_CREATE);
-
- PyErr_NoMemory();
- return -1;
- }
-
- *pv = result;
- v = result;
- _Py_NewReferenceNoTotal(v);
- PyBytesObject *sv = (PyBytesObject *)v;
- Py_SET_SIZE(sv, newsize);
- sv->ob_sval[newsize] = '\0';
- set_ob_shash(sv, -1); /* invalidate cached hash value */
- assert(_PyBytes_IsMutable(*pv));
- return 0;
+ return bytes_resize_inplace(pv, newsize);
}
@@ -3903,9 +3924,6 @@ PyBytesWriter_FinishWithSize(PyBytesWriter *writer,
Py_ssize_t size)
{
assert(byteswriter_check_consistency(writer));
- // Check for negative size here to raise ValueError in all cases, rather
- // than having a different exception depending on the code path. For
- // example, _PyBytes_Resize() raises SystemError on negative size.
if (size < 0) {
PyErr_Format(PyExc_ValueError, "size must be positive");
goto error;
@@ -3919,6 +3937,13 @@ PyBytesWriter_FinishWithSize(PyBytesWriter *writer,
Py_ssize_t size)
PyObject *result;
if (size == 0 && !writer->use_bytearray) {
result = bytes_get_empty();
+ if (writer->obj != NULL) {
+#ifdef Py_DEBUG
+ byteswriter_reset_trailing_byte(writer);
+#endif
+ Py_DECREF(writer->obj);
+ writer->obj = NULL;
+ }
}
else if (writer->obj != NULL) {
// Truncate the bytes/bytearray object if needed
@@ -3935,14 +3960,11 @@ PyBytesWriter_FinishWithSize(PyBytesWriter *writer,
Py_ssize_t size)
}
else {
if (size == 1) {
- // Get the single byte singleton
unsigned char ch = PyBytes_AS_STRING(writer->obj)[0];
- PyObject *op = (PyObject*)CHARACTER(ch);
- assert(_Py_IsImmortal(op));
- Py_SETREF(writer->obj, op);
+ Py_SETREF(writer->obj, bytes_get_char(ch));
}
else if (size != PyBytes_GET_SIZE(writer->obj)) {
- if (_PyBytes_Resize(&writer->obj, size)) {
+ if (bytes_resize_inplace(&writer->obj, size)) {
goto error;
}
}
@@ -3953,23 +3975,26 @@ PyBytesWriter_FinishWithSize(PyBytesWriter *writer,
Py_ssize_t size)
}
else {
// Create an object from the small buffer
+ const char *buffer = (const char *)writer->small_buffer;
if (writer->use_bytearray) {
- result = PyByteArray_FromStringAndSize(writer->small_buffer, size);
+ result = PyByteArray_FromStringAndSize(buffer, size);
}
else {
- // The function returns single byte singleton if size equals 1
- result = PyBytes_FromStringAndSize(writer->small_buffer, size);
+ if (size == 1) {
+ result = bytes_get_char((uint8_t)buffer[0]);
+ }
+ else {
+ result = _PyBytes_FromSize(size, 0);
+ if (result == NULL) {
+ goto error;
+ }
+ memcpy(PyBytes_AS_STRING(result), buffer, size);
+ }
}
}
-#ifdef Py_DEBUG
- // Reset the writer, so byteswriter_check_consistency() doesn't fail
- // in PyBytesWriter_Discard().
- writer->size = 0;
- byteswriter_write_canary_byte(writer);
-#endif
-
- PyBytesWriter_Discard(writer);
+ assert(writer->obj == NULL);
+ _Py_FREELIST_FREE(bytes_writers, writer, PyMem_Free);
return result;
error:
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]