Skip to content

Commit 3e41f3c

Browse files
sir-sigurdmethane
authored andcommitted
bpo-34488: optimize BytesIO.writelines() (GH-8904)
Avoid the creation of unused int object for each line.
1 parent 3a5c433 commit 3e41f3c

File tree

2 files changed

+61
-36
lines changed

2 files changed

+61
-36
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:meth:`writelines` method of :class:`io.BytesIO` is now slightly faster
2+
when many small lines are passed. Patch by Sergey Fedoseev.

Modules/_io/bytesio.c

Lines changed: 59 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,34 @@ typedef struct {
3131
* exports > 0. Py_REFCNT(buf) == 1, any modifications are forbidden.
3232
*/
3333

34+
static int
35+
check_closed(bytesio *self)
36+
{
37+
if (self->buf == NULL) {
38+
PyErr_SetString(PyExc_ValueError, "I/O operation on closed file.");
39+
return 1;
40+
}
41+
return 0;
42+
}
43+
44+
static int
45+
check_exports(bytesio *self)
46+
{
47+
if (self->exports > 0) {
48+
PyErr_SetString(PyExc_BufferError,
49+
"Existing exports of data: object cannot be re-sized");
50+
return 1;
51+
}
52+
return 0;
53+
}
54+
3455
#define CHECK_CLOSED(self) \
35-
if ((self)->buf == NULL) { \
36-
PyErr_SetString(PyExc_ValueError, \
37-
"I/O operation on closed file."); \
56+
if (check_closed(self)) { \
3857
return NULL; \
3958
}
4059

4160
#define CHECK_EXPORTS(self) \
42-
if ((self)->exports > 0) { \
43-
PyErr_SetString(PyExc_BufferError, \
44-
"Existing exports of data: object cannot be re-sized"); \
61+
if (check_exports(self)) { \
4562
return NULL; \
4663
}
4764

@@ -156,23 +173,41 @@ resize_buffer(bytesio *self, size_t size)
156173
}
157174

158175
/* Internal routine for writing a string of bytes to the buffer of a BytesIO
159-
object. Returns the number of bytes written, or -1 on error. */
160-
static Py_ssize_t
161-
write_bytes(bytesio *self, const char *bytes, Py_ssize_t len)
176+
object. Returns the number of bytes written, or -1 on error.
177+
Inlining is disabled because it's significantly decreases performance
178+
of writelines() in PGO build. */
179+
_Py_NO_INLINE static Py_ssize_t
180+
write_bytes(bytesio *self, PyObject *b)
162181
{
163-
size_t endpos;
164-
assert(self->buf != NULL);
165-
assert(self->pos >= 0);
166-
assert(len >= 0);
182+
if (check_closed(self)) {
183+
return -1;
184+
}
185+
if (check_exports(self)) {
186+
return -1;
187+
}
167188

168-
endpos = (size_t)self->pos + len;
189+
Py_buffer buf;
190+
if (PyObject_GetBuffer(b, &buf, PyBUF_CONTIG_RO) < 0) {
191+
return -1;
192+
}
193+
Py_ssize_t len = buf.len;
194+
if (len == 0) {
195+
goto done;
196+
}
197+
198+
assert(self->pos >= 0);
199+
size_t endpos = (size_t)self->pos + len;
169200
if (endpos > (size_t)PyBytes_GET_SIZE(self->buf)) {
170-
if (resize_buffer(self, endpos) < 0)
171-
return -1;
201+
if (resize_buffer(self, endpos) < 0) {
202+
len = -1;
203+
goto done;
204+
}
172205
}
173206
else if (SHARED_BUF(self)) {
174-
if (unshare_buffer(self, Py_MAX(endpos, (size_t)self->string_size)) < 0)
175-
return -1;
207+
if (unshare_buffer(self, Py_MAX(endpos, (size_t)self->string_size)) < 0) {
208+
len = -1;
209+
goto done;
210+
}
176211
}
177212

178213
if (self->pos > self->string_size) {
@@ -190,14 +225,16 @@ write_bytes(bytesio *self, const char *bytes, Py_ssize_t len)
190225

191226
/* Copy the data to the internal buffer, overwriting some of the existing
192227
data if self->pos < self->string_size. */
193-
memcpy(PyBytes_AS_STRING(self->buf) + self->pos, bytes, len);
228+
memcpy(PyBytes_AS_STRING(self->buf) + self->pos, buf.buf, len);
194229
self->pos = endpos;
195230

196231
/* Set the new length of the internal string if it has changed. */
197232
if ((size_t)self->string_size < endpos) {
198233
self->string_size = endpos;
199234
}
200235

236+
done:
237+
PyBuffer_Release(&buf);
201238
return len;
202239
}
203240

@@ -669,19 +706,7 @@ static PyObject *
669706
_io_BytesIO_write(bytesio *self, PyObject *b)
670707
/*[clinic end generated code: output=53316d99800a0b95 input=f5ec7c8c64ed720a]*/
671708
{
672-
Py_ssize_t n = 0;
673-
Py_buffer buf;
674-
675-
CHECK_CLOSED(self);
676-
CHECK_EXPORTS(self);
677-
678-
if (PyObject_GetBuffer(b, &buf, PyBUF_CONTIG_RO) < 0)
679-
return NULL;
680-
681-
if (buf.len != 0)
682-
n = write_bytes(self, buf.buf, buf.len);
683-
684-
PyBuffer_Release(&buf);
709+
Py_ssize_t n = write_bytes(self, b);
685710
return n >= 0 ? PyLong_FromSsize_t(n) : NULL;
686711
}
687712

@@ -702,7 +727,6 @@ _io_BytesIO_writelines(bytesio *self, PyObject *lines)
702727
/*[clinic end generated code: output=7f33aa3271c91752 input=e972539176fc8fc1]*/
703728
{
704729
PyObject *it, *item;
705-
PyObject *ret;
706730

707731
CHECK_CLOSED(self);
708732

@@ -711,13 +735,12 @@ _io_BytesIO_writelines(bytesio *self, PyObject *lines)
711735
return NULL;
712736

713737
while ((item = PyIter_Next(it)) != NULL) {
714-
ret = _io_BytesIO_write(self, item);
738+
Py_ssize_t ret = write_bytes(self, item);
715739
Py_DECREF(item);
716-
if (ret == NULL) {
740+
if (ret < 0) {
717741
Py_DECREF(it);
718742
return NULL;
719743
}
720-
Py_DECREF(ret);
721744
}
722745
Py_DECREF(it);
723746

0 commit comments

Comments
 (0)