From 785b4d98024802a32d89a732621949a0603d9331 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Fri, 2 Oct 2026 01:16:42 +0200 Subject: [PATCH] gh-158585: Don't overallocate at first PyBytesWriter_Resize() PyBytesWriter_Resize() and PyBytesWriter_Grow() no longer overallocate when the first bytes/bytearray object is allocated. Only overallocate bytes objects at next PyBytesWriter_Resize() and PyBytesWriter_Grow() calls. --- ...6-10-02-20-22-47.gh-issue-158585.1NXX75.rst | 4 ++++ Objects/bytesobject.c | 18 +++++++++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/C_API/2026-10-02-20-22-47.gh-issue-158585.1NXX75.rst diff --git a/Misc/NEWS.d/next/C_API/2026-10-02-20-22-47.gh-issue-158585.1NXX75.rst b/Misc/NEWS.d/next/C_API/2026-10-02-20-22-47.gh-issue-158585.1NXX75.rst new file mode 100644 index 00000000000000..211b03d35ec9f5 --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2026-10-02-20-22-47.gh-issue-158585.1NXX75.rst @@ -0,0 +1,4 @@ +:c:func:`PyBytesWriter_Resize` and :c:func:`PyBytesWriter_Grow` no longer +overallocate when the first bytes/bytearray object is allocated. Only +overallocate bytes objects at next :c:func:`PyBytesWriter_Resize` and +:c:func:`PyBytesWriter_Grow` calls. Patch by Victor Stinner. diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c index 7e677108983f0f..a30fd53b68f218 100644 --- a/Objects/bytesobject.c +++ b/Objects/bytesobject.c @@ -3755,13 +3755,15 @@ byteswriter_resize(PyBytesWriter *writer, Py_ssize_t new_size, int resize) } Py_ssize_t alloc = new_size; - if (resize && writer->overallocate) { - if (alloc <= (PY_SSIZE_T_MAX - alloc / OVERALLOCATE_FACTOR)) { - alloc += alloc / OVERALLOCATE_FACTOR; + if (writer->obj != NULL) { + // Overallocate the bytes object. Do not overallocate bytearray or + // if overallocation was disabled (using the private C API). + if (writer->overallocate) { + if (alloc <= (PY_SSIZE_T_MAX - alloc / OVERALLOCATE_FACTOR)) { + alloc += alloc / OVERALLOCATE_FACTOR; + } } - } - if (writer->obj != NULL) { if (writer->use_bytearray) { if (PyByteArray_Resize(writer->obj, alloc)) { #ifdef Py_DEBUG @@ -3784,6 +3786,12 @@ byteswriter_resize(PyBytesWriter *writer, Py_ssize_t new_size, int resize) assert(writer->obj != NULL); } else { + // gh-158585: Optimistic allocation strategy: don't overallocate when + // the first bytes/bytearray object is created. The bet is that the + // most common case is that a single bytes/bytearray is needed for the + // whole writer lifecycle. In that case, PyBytesWriter_Finish() doesn't + // need to truncate the bytes/bytearray object. + char *data; if (writer->use_bytearray) { writer->obj = PyByteArray_FromStringAndSize(NULL, alloc);