Skip to content

Commit 850687d

Browse files
authored
bpo-47070: Add _PyBytes_Repeat() (GH-31999)
Use it where appropriate: the repeat functions of `array.array`, `bytes`, `bytearray`, and `str`.
1 parent 86384cf commit 850687d

File tree

6 files changed

+81
-103
lines changed

6 files changed

+81
-103
lines changed

Include/internal/pycore_bytesobject.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,19 @@ _PyBytes_ReverseFind(const char *haystack, Py_ssize_t len_haystack,
3333
const char *needle, Py_ssize_t len_needle,
3434
Py_ssize_t offset);
3535

36+
37+
/** Helper function to implement the repeat and inplace repeat methods on a buffer
38+
*
39+
* len_dest is assumed to be an integer multiple of len_src.
40+
* If src equals dest, then assume the operation is inplace.
41+
*
42+
* This method repeately doubles the number of bytes copied to reduce
43+
* the number of invocations of memcpy.
44+
*/
45+
PyAPI_FUNC(void)
46+
_PyBytes_Repeat(char* dest, Py_ssize_t len_dest,
47+
const char* src, Py_ssize_t len_src);
48+
3649
#ifdef __cplusplus
3750
}
3851
#endif
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Improve performance of ``array_inplace_repeat`` by reducing the number of invocations of ``memcpy``.
2+
Refactor the ``repeat`` and inplace ``repeat`` methods of ``array``, ``bytes``, ``bytearray``
3+
and ``unicodeobject`` to use the common ``_PyBytes_Repeat``.

Modules/arraymodule.c

Lines changed: 18 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#define PY_SSIZE_T_CLEAN
1111
#include "Python.h"
1212
#include "pycore_moduleobject.h" // _PyModule_GetState()
13+
#include "pycore_bytesobject.h" // _PyBytes_Repeat
1314
#include "structmember.h" // PyMemberDef
1415
#include <stddef.h> // offsetof()
1516
#include <stddef.h>
@@ -910,34 +911,24 @@ static PyObject *
910911
array_repeat(arrayobject *a, Py_ssize_t n)
911912
{
912913
array_state *state = find_array_state_by_type(Py_TYPE(a));
913-
Py_ssize_t size;
914-
arrayobject *np;
915-
Py_ssize_t oldbytes, newbytes;
914+
916915
if (n < 0)
917916
n = 0;
918-
if ((Py_SIZE(a) != 0) && (n > PY_SSIZE_T_MAX / Py_SIZE(a))) {
917+
const Py_ssize_t array_length = Py_SIZE(a);
918+
if ((array_length != 0) && (n > PY_SSIZE_T_MAX / array_length)) {
919919
return PyErr_NoMemory();
920920
}
921-
size = Py_SIZE(a) * n;
922-
np = (arrayobject *) newarrayobject(state->ArrayType, size, a->ob_descr);
921+
Py_ssize_t size = array_length * n;
922+
arrayobject* np = (arrayobject *) newarrayobject(state->ArrayType, size, a->ob_descr);
923923
if (np == NULL)
924924
return NULL;
925925
if (size == 0)
926926
return (PyObject *)np;
927-
oldbytes = Py_SIZE(a) * a->ob_descr->itemsize;
928-
newbytes = oldbytes * n;
929-
/* this follows the code in unicode_repeat */
930-
if (oldbytes == 1) {
931-
memset(np->ob_item, a->ob_item[0], newbytes);
932-
} else {
933-
Py_ssize_t done = oldbytes;
934-
memcpy(np->ob_item, a->ob_item, oldbytes);
935-
while (done < newbytes) {
936-
Py_ssize_t ncopy = (done <= newbytes-done) ? done : newbytes-done;
937-
memcpy(np->ob_item+done, np->ob_item, ncopy);
938-
done += ncopy;
939-
}
940-
}
927+
928+
const Py_ssize_t oldbytes = array_length * a->ob_descr->itemsize;
929+
const Py_ssize_t newbytes = oldbytes * n;
930+
_PyBytes_Repeat(np->ob_item, newbytes, a->ob_item, oldbytes);
931+
941932
return (PyObject *)np;
942933
}
943934

@@ -1075,27 +1066,23 @@ array_inplace_concat(arrayobject *self, PyObject *bb)
10751066
static PyObject *
10761067
array_inplace_repeat(arrayobject *self, Py_ssize_t n)
10771068
{
1078-
char *items, *p;
1079-
Py_ssize_t size, i;
1069+
const Py_ssize_t array_size = Py_SIZE(self);
10801070

1081-
if (Py_SIZE(self) > 0) {
1071+
if (array_size > 0 && n != 1 ) {
10821072
if (n < 0)
10831073
n = 0;
10841074
if ((self->ob_descr->itemsize != 0) &&
1085-
(Py_SIZE(self) > PY_SSIZE_T_MAX / self->ob_descr->itemsize)) {
1075+
(array_size > PY_SSIZE_T_MAX / self->ob_descr->itemsize)) {
10861076
return PyErr_NoMemory();
10871077
}
1088-
size = Py_SIZE(self) * self->ob_descr->itemsize;
1078+
Py_ssize_t size = array_size * self->ob_descr->itemsize;
10891079
if (n > 0 && size > PY_SSIZE_T_MAX / n) {
10901080
return PyErr_NoMemory();
10911081
}
1092-
if (array_resize(self, n * Py_SIZE(self)) == -1)
1082+
if (array_resize(self, n * array_size) == -1)
10931083
return NULL;
1094-
items = p = self->ob_item;
1095-
for (i = 1; i < n; i++) {
1096-
p += size;
1097-
memcpy(p, items, size);
1098-
}
1084+
1085+
_PyBytes_Repeat(self->ob_item, n*size, self->ob_item, size);
10991086
}
11001087
Py_INCREF(self);
11011088
return (PyObject *)self;

Objects/bytearrayobject.c

Lines changed: 15 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "Python.h"
55
#include "pycore_abstract.h" // _PyIndex_Check()
66
#include "pycore_bytes_methods.h"
7+
#include "pycore_bytesobject.h"
78
#include "pycore_object.h" // _PyObject_GC_UNTRACK()
89
#include "pycore_strhex.h" // _Py_strhex_with_sep()
910
#include "pycore_long.h" // _PyLong_FromUnsignedChar()
@@ -319,71 +320,39 @@ bytearray_iconcat(PyByteArrayObject *self, PyObject *other)
319320
static PyObject *
320321
bytearray_repeat(PyByteArrayObject *self, Py_ssize_t count)
321322
{
322-
PyByteArrayObject *result;
323-
Py_ssize_t mysize;
324-
Py_ssize_t size;
325-
const char *buf;
326-
327323
if (count < 0)
328324
count = 0;
329-
mysize = Py_SIZE(self);
325+
const Py_ssize_t mysize = Py_SIZE(self);
330326
if (count > 0 && mysize > PY_SSIZE_T_MAX / count)
331327
return PyErr_NoMemory();
332-
size = mysize * count;
333-
result = (PyByteArrayObject *)PyByteArray_FromStringAndSize(NULL, size);
334-
buf = PyByteArray_AS_STRING(self);
328+
Py_ssize_t size = mysize * count;
329+
PyByteArrayObject* result = (PyByteArrayObject *)PyByteArray_FromStringAndSize(NULL, size);
330+
const char* buf = PyByteArray_AS_STRING(self);
335331
if (result != NULL && size != 0) {
336-
if (mysize == 1)
337-
memset(result->ob_bytes, buf[0], size);
338-
else {
339-
Py_ssize_t i, j;
340-
341-
i = 0;
342-
if (i < size) {
343-
memcpy(result->ob_bytes, buf, mysize);
344-
i = mysize;
345-
}
346-
// repeatedly double the number of bytes copied
347-
while (i < size) {
348-
j = Py_MIN(i, size - i);
349-
memcpy(result->ob_bytes + i, result->ob_bytes, j);
350-
i += j;
351-
}
352-
}
332+
_PyBytes_Repeat(result->ob_bytes, size, buf, mysize);
353333
}
354334
return (PyObject *)result;
355335
}
356336

357337
static PyObject *
358338
bytearray_irepeat(PyByteArrayObject *self, Py_ssize_t count)
359339
{
360-
Py_ssize_t mysize;
361-
Py_ssize_t size;
362-
char *buf;
363-
364340
if (count < 0)
365341
count = 0;
366-
mysize = Py_SIZE(self);
342+
else if (count == 1) {
343+
Py_INCREF(self);
344+
return (PyObject*)self;
345+
}
346+
347+
const Py_ssize_t mysize = Py_SIZE(self);
367348
if (count > 0 && mysize > PY_SSIZE_T_MAX / count)
368349
return PyErr_NoMemory();
369-
size = mysize * count;
350+
const Py_ssize_t size = mysize * count;
370351
if (PyByteArray_Resize((PyObject *)self, size) < 0)
371352
return NULL;
372353

373-
buf = PyByteArray_AS_STRING(self);
374-
if (mysize == 1)
375-
memset(buf, buf[0], size);
376-
else {
377-
Py_ssize_t i, j;
378-
379-
i = mysize;
380-
// repeatedly double the number of bytes copied
381-
while (i < size) {
382-
j = Py_MIN(i, size - i);
383-
memcpy(buf + i, buf, j);
384-
i += j;
385-
}
386-
}
354+
char* buf = PyByteArray_AS_STRING(self);
355+
_PyBytes_Repeat(buf, size, buf, mysize);
387356

388357
Py_INCREF(self);
389358
return (PyObject *)self;

Objects/bytesobject.c

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
#include "Python.h"
66
#include "pycore_abstract.h" // _PyIndex_Check()
7-
#include "pycore_bytesobject.h" // _PyBytes_Find()
7+
#include "pycore_bytesobject.h" // _PyBytes_Find(), _PyBytes_Repeat()
88
#include "pycore_bytes_methods.h" // _Py_bytes_startswith()
99
#include "pycore_call.h" // _PyObject_CallNoArgs()
1010
#include "pycore_format.h" // F_LJUST
@@ -1421,8 +1421,6 @@ bytes_concat(PyObject *a, PyObject *b)
14211421
static PyObject *
14221422
bytes_repeat(PyBytesObject *a, Py_ssize_t n)
14231423
{
1424-
Py_ssize_t i;
1425-
Py_ssize_t j;
14261424
Py_ssize_t size;
14271425
PyBytesObject *op;
14281426
size_t nbytes;
@@ -1457,20 +1455,9 @@ _Py_COMP_DIAG_IGNORE_DEPR_DECLS
14571455
op->ob_shash = -1;
14581456
_Py_COMP_DIAG_POP
14591457
op->ob_sval[size] = '\0';
1460-
if (Py_SIZE(a) == 1 && n > 0) {
1461-
memset(op->ob_sval, a->ob_sval[0] , n);
1462-
return (PyObject *) op;
1463-
}
1464-
i = 0;
1465-
if (i < size) {
1466-
memcpy(op->ob_sval, a->ob_sval, Py_SIZE(a));
1467-
i = Py_SIZE(a);
1468-
}
1469-
while (i < size) {
1470-
j = (i <= size-i) ? i : size-i;
1471-
memcpy(op->ob_sval+i, op->ob_sval, j);
1472-
i += j;
1473-
}
1458+
1459+
_PyBytes_Repeat(op->ob_sval, size, a->ob_sval, Py_SIZE(a));
1460+
14741461
return (PyObject *) op;
14751462
}
14761463

@@ -3528,3 +3515,28 @@ _PyBytesWriter_WriteBytes(_PyBytesWriter *writer, void *ptr,
35283515

35293516
return str;
35303517
}
3518+
3519+
3520+
void
3521+
_PyBytes_Repeat(char* dest, Py_ssize_t len_dest,
3522+
const char* src, Py_ssize_t len_src)
3523+
{
3524+
if (len_dest == 0) {
3525+
return;
3526+
}
3527+
if (len_src == 1) {
3528+
memset(dest, src[0], len_dest);
3529+
}
3530+
else {
3531+
if (src != dest) {
3532+
memcpy(dest, src, len_src);
3533+
}
3534+
Py_ssize_t copied = len_src;
3535+
while (copied < len_dest) {
3536+
Py_ssize_t bytes_to_copy = Py_MIN(copied, len_dest - copied);
3537+
memcpy(dest + copied, dest, bytes_to_copy);
3538+
copied += bytes_to_copy;
3539+
}
3540+
}
3541+
}
3542+

Objects/unicodeobject.c

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
4242
#include "Python.h"
4343
#include "pycore_abstract.h" // _PyIndex_Check()
4444
#include "pycore_atomic_funcs.h" // _Py_atomic_size_get()
45+
#include "pycore_bytesobject.h" // _PyBytes_Repeat()
4546
#include "pycore_bytes_methods.h" // _Py_bytes_lower()
4647
#include "pycore_format.h" // F_LJUST
4748
#include "pycore_initconfig.h" // _PyStatus_OK()
@@ -12782,17 +12783,10 @@ unicode_repeat(PyObject *str, Py_ssize_t len)
1278212783
}
1278312784
}
1278412785
else {
12785-
/* number of characters copied this far */
12786-
Py_ssize_t done = PyUnicode_GET_LENGTH(str);
1278712786
Py_ssize_t char_size = PyUnicode_KIND(str);
1278812787
char *to = (char *) PyUnicode_DATA(u);
12789-
memcpy(to, PyUnicode_DATA(str),
12790-
PyUnicode_GET_LENGTH(str) * char_size);
12791-
while (done < nchars) {
12792-
n = (done <= nchars-done) ? done : nchars-done;
12793-
memcpy(to + (done * char_size), to, n * char_size);
12794-
done += n;
12795-
}
12788+
_PyBytes_Repeat(to, nchars * char_size, PyUnicode_DATA(str),
12789+
PyUnicode_GET_LENGTH(str) * char_size);
1279612790
}
1279712791

1279812792
assert(_PyUnicode_CheckConsistency(u, 1));

0 commit comments

Comments
 (0)