bpo-46235: Do all ref-counting at once during list/tuple multiplication (GH-30346)

When multiplying lists and tuples by `n`, increment each element's refcount, by `n`, just once.

Saves `n-1` increments per element, and allows for a leaner & faster copying loop.

Code by  sweeneyde (Dennis Sweeney).
This commit is contained in:
Dennis Sweeney 2022-01-07 22:47:58 -05:00 committed by GitHub
parent 6fa8b2ceee
commit ad1d5908ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 49 additions and 26 deletions

View File

@ -0,0 +1 @@
Certain sequence multiplication operations like ``[0] * 1_000`` are now faster due to reference-counting optimizations. Patch by Dennis Sweeney.

View File

@ -553,11 +553,8 @@ list_concat(PyListObject *a, PyObject *bb)
static PyObject * static PyObject *
list_repeat(PyListObject *a, Py_ssize_t n) list_repeat(PyListObject *a, Py_ssize_t n)
{ {
Py_ssize_t i, j;
Py_ssize_t size; Py_ssize_t size;
PyListObject *np; PyListObject *np;
PyObject **p, **items;
PyObject *elem;
if (n < 0) if (n < 0)
n = 0; n = 0;
if (n > 0 && Py_SIZE(a) > PY_SSIZE_T_MAX / n) if (n > 0 && Py_SIZE(a) > PY_SSIZE_T_MAX / n)
@ -568,24 +565,32 @@ list_repeat(PyListObject *a, Py_ssize_t n)
np = (PyListObject *) list_new_prealloc(size); np = (PyListObject *) list_new_prealloc(size);
if (np == NULL) if (np == NULL)
return NULL; return NULL;
PyObject **dest = np->ob_item;
PyObject **dest_end = dest + size;
if (Py_SIZE(a) == 1) { if (Py_SIZE(a) == 1) {
items = np->ob_item; PyObject *elem = a->ob_item[0];
elem = a->ob_item[0]; Py_SET_REFCNT(elem, Py_REFCNT(elem) + n);
for (i = 0; i < n; i++) { #ifdef Py_REF_DEBUG
items[i] = elem; _Py_RefTotal += n;
Py_INCREF(elem); #endif
while (dest < dest_end) {
*dest++ = elem;
} }
} }
else { else {
p = np->ob_item; PyObject **src = a->ob_item;
items = a->ob_item; PyObject **src_end = src + Py_SIZE(a);
for (i = 0; i < n; i++) { while (src < src_end) {
for (j = 0; j < Py_SIZE(a); j++) { Py_SET_REFCNT(*src, Py_REFCNT(*src) + n);
*p = items[j]; #ifdef Py_REF_DEBUG
Py_INCREF(*p); _Py_RefTotal += n;
p++; #endif
} *dest++ = *src++;
}
// Now src chases after dest in the same buffer
src = np->ob_item;
while (dest < dest_end) {
*dest++ = *src++;
} }
} }
Py_SET_SIZE(np, size); Py_SET_SIZE(np, size);

View File

@ -589,10 +589,8 @@ tupleconcat(PyTupleObject *a, PyObject *bb)
static PyObject * static PyObject *
tuplerepeat(PyTupleObject *a, Py_ssize_t n) tuplerepeat(PyTupleObject *a, Py_ssize_t n)
{ {
Py_ssize_t i, j;
Py_ssize_t size; Py_ssize_t size;
PyTupleObject *np; PyTupleObject *np;
PyObject **p, **items;
if (Py_SIZE(a) == 0 || n == 1) { if (Py_SIZE(a) == 0 || n == 1) {
if (PyTuple_CheckExact(a)) { if (PyTuple_CheckExact(a)) {
/* Since tuples are immutable, we can return a shared /* Since tuples are immutable, we can return a shared
@ -610,13 +608,32 @@ tuplerepeat(PyTupleObject *a, Py_ssize_t n)
np = tuple_alloc(size); np = tuple_alloc(size);
if (np == NULL) if (np == NULL)
return NULL; return NULL;
p = np->ob_item; PyObject **dest = np->ob_item;
items = a->ob_item; PyObject **dest_end = dest + size;
for (i = 0; i < n; i++) { if (Py_SIZE(a) == 1) {
for (j = 0; j < Py_SIZE(a); j++) { PyObject *elem = a->ob_item[0];
*p = items[j]; Py_SET_REFCNT(elem, Py_REFCNT(elem) + n);
Py_INCREF(*p); #ifdef Py_REF_DEBUG
p++; _Py_RefTotal += n;
#endif
while (dest < dest_end) {
*dest++ = elem;
}
}
else {
PyObject **src = a->ob_item;
PyObject **src_end = src + Py_SIZE(a);
while (src < src_end) {
Py_SET_REFCNT(*src, Py_REFCNT(*src) + n);
#ifdef Py_REF_DEBUG
_Py_RefTotal += n;
#endif
*dest++ = *src++;
}
// Now src chases after dest in the same buffer
src = np->ob_item;
while (dest < dest_end) {
*dest++ = *src++;
} }
} }
_PyObject_GC_TRACK(np); _PyObject_GC_TRACK(np);