Skip to content

Commit ad1d590

Browse files
authored
bpo-46235: Do all ref-counting at once during list/tuple multiplication (pythonGH-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).
1 parent 6fa8b2c commit ad1d590

File tree

3 files changed

+49
-26
lines changed

3 files changed

+49
-26
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Certain sequence multiplication operations like ``[0] * 1_000`` are now faster due to reference-counting optimizations. Patch by Dennis Sweeney.

Objects/listobject.c

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -553,11 +553,8 @@ list_concat(PyListObject *a, PyObject *bb)
553553
static PyObject *
554554
list_repeat(PyListObject *a, Py_ssize_t n)
555555
{
556-
Py_ssize_t i, j;
557556
Py_ssize_t size;
558557
PyListObject *np;
559-
PyObject **p, **items;
560-
PyObject *elem;
561558
if (n < 0)
562559
n = 0;
563560
if (n > 0 && Py_SIZE(a) > PY_SSIZE_T_MAX / n)
@@ -568,24 +565,32 @@ list_repeat(PyListObject *a, Py_ssize_t n)
568565
np = (PyListObject *) list_new_prealloc(size);
569566
if (np == NULL)
570567
return NULL;
571-
568+
PyObject **dest = np->ob_item;
569+
PyObject **dest_end = dest + size;
572570
if (Py_SIZE(a) == 1) {
573-
items = np->ob_item;
574-
elem = a->ob_item[0];
575-
for (i = 0; i < n; i++) {
576-
items[i] = elem;
577-
Py_INCREF(elem);
571+
PyObject *elem = a->ob_item[0];
572+
Py_SET_REFCNT(elem, Py_REFCNT(elem) + n);
573+
#ifdef Py_REF_DEBUG
574+
_Py_RefTotal += n;
575+
#endif
576+
while (dest < dest_end) {
577+
*dest++ = elem;
578578
}
579579
}
580580
else {
581-
p = np->ob_item;
582-
items = a->ob_item;
583-
for (i = 0; i < n; i++) {
584-
for (j = 0; j < Py_SIZE(a); j++) {
585-
*p = items[j];
586-
Py_INCREF(*p);
587-
p++;
588-
}
581+
PyObject **src = a->ob_item;
582+
PyObject **src_end = src + Py_SIZE(a);
583+
while (src < src_end) {
584+
Py_SET_REFCNT(*src, Py_REFCNT(*src) + n);
585+
#ifdef Py_REF_DEBUG
586+
_Py_RefTotal += n;
587+
#endif
588+
*dest++ = *src++;
589+
}
590+
// Now src chases after dest in the same buffer
591+
src = np->ob_item;
592+
while (dest < dest_end) {
593+
*dest++ = *src++;
589594
}
590595
}
591596
Py_SET_SIZE(np, size);

Objects/tupleobject.c

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -589,10 +589,8 @@ tupleconcat(PyTupleObject *a, PyObject *bb)
589589
static PyObject *
590590
tuplerepeat(PyTupleObject *a, Py_ssize_t n)
591591
{
592-
Py_ssize_t i, j;
593592
Py_ssize_t size;
594593
PyTupleObject *np;
595-
PyObject **p, **items;
596594
if (Py_SIZE(a) == 0 || n == 1) {
597595
if (PyTuple_CheckExact(a)) {
598596
/* Since tuples are immutable, we can return a shared
@@ -610,13 +608,32 @@ tuplerepeat(PyTupleObject *a, Py_ssize_t n)
610608
np = tuple_alloc(size);
611609
if (np == NULL)
612610
return NULL;
613-
p = np->ob_item;
614-
items = a->ob_item;
615-
for (i = 0; i < n; i++) {
616-
for (j = 0; j < Py_SIZE(a); j++) {
617-
*p = items[j];
618-
Py_INCREF(*p);
619-
p++;
611+
PyObject **dest = np->ob_item;
612+
PyObject **dest_end = dest + size;
613+
if (Py_SIZE(a) == 1) {
614+
PyObject *elem = a->ob_item[0];
615+
Py_SET_REFCNT(elem, Py_REFCNT(elem) + n);
616+
#ifdef Py_REF_DEBUG
617+
_Py_RefTotal += n;
618+
#endif
619+
while (dest < dest_end) {
620+
*dest++ = elem;
621+
}
622+
}
623+
else {
624+
PyObject **src = a->ob_item;
625+
PyObject **src_end = src + Py_SIZE(a);
626+
while (src < src_end) {
627+
Py_SET_REFCNT(*src, Py_REFCNT(*src) + n);
628+
#ifdef Py_REF_DEBUG
629+
_Py_RefTotal += n;
630+
#endif
631+
*dest++ = *src++;
632+
}
633+
// Now src chases after dest in the same buffer
634+
src = np->ob_item;
635+
while (dest < dest_end) {
636+
*dest++ = *src++;
620637
}
621638
}
622639
_PyObject_GC_TRACK(np);

0 commit comments

Comments
 (0)