Skip to content

Commit 2ce845d

Browse files
committed
Copy deque elements directly in deque_copy
1 parent 8885f31 commit 2ce845d

1 file changed

Lines changed: 29 additions & 32 deletions

File tree

Modules/_collectionsmodule.c

Lines changed: 29 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -605,29 +605,42 @@ deque_copy_impl(dequeobject *deque)
605605
collections_state *state = find_module_state_by_def(Py_TYPE(deque));
606606
if (Py_IS_TYPE(deque, state->deque_type)) {
607607
dequeobject *new_deque;
608-
PyObject *rv;
608+
Py_ssize_t n = Py_SIZE(deque);
609609

610610
new_deque = (dequeobject *)deque_new(state->deque_type, NULL, NULL);
611611
if (new_deque == NULL)
612612
return NULL;
613613
new_deque->maxlen = old_deque->maxlen;
614-
/* Fast path for the deque_repeat() common case where len(deque) == 1
615-
*
616-
* It's safe to not acquire the per-object lock for new_deque; it's
617-
* invisible to other threads.
614+
615+
/* Copy elements directly by walking the block structure.
616+
* This is safe because the caller holds the deque lock and
617+
* the new deque is not yet visible to other threads.
618618
*/
619-
if (Py_SIZE(deque) == 1) {
620-
PyObject *item = old_deque->leftblock->data[old_deque->leftindex];
621-
rv = deque_append_impl(new_deque, item);
622-
} else {
623-
rv = deque_extend_impl(new_deque, (PyObject *)deque);
624-
}
625-
if (rv != NULL) {
626-
Py_DECREF(rv);
627-
return (PyObject *)new_deque;
619+
if (n > 0) {
620+
block *b = old_deque->leftblock;
621+
Py_ssize_t index = old_deque->leftindex;
622+
623+
/* Space saving heuristic. Start filling from the left */
624+
assert(new_deque->leftblock == new_deque->rightblock);
625+
assert(new_deque->leftindex == new_deque->rightindex + 1);
626+
new_deque->leftindex = 1;
627+
new_deque->rightindex = 0;
628+
629+
for (Py_ssize_t i = 0; i < n; i++) {
630+
PyObject *item = b->data[index];
631+
if (deque_append_lock_held(new_deque, Py_NewRef(item),
632+
new_deque->maxlen) < 0) {
633+
Py_DECREF(new_deque);
634+
return NULL;
635+
}
636+
index++;
637+
if (index == BLOCKLEN) {
638+
b = b->rightlink;
639+
index = 0;
640+
}
641+
}
628642
}
629-
Py_DECREF(new_deque);
630-
return NULL;
643+
return (PyObject *)new_deque;
631644
}
632645
if (old_deque->maxlen < 0)
633646
result = PyObject_CallOneArg((PyObject *)(Py_TYPE(deque)),
@@ -1983,22 +1996,6 @@ dequeiter_next(PyObject *op)
19831996
// It's safe to access it->deque without holding the per-object lock for it
19841997
// here; it->deque is only assigned during construction of it.
19851998
dequeobject *deque = it->deque;
1986-
1987-
#ifdef Py_GIL_DISABLED
1988-
// gh-144809: When called from deque_copy(), the deque is already
1989-
// locked. The two-object critical section below would unlock and
1990-
// re-lock the deque between calls, allowing another thread to modify
1991-
// it mid-iteration. The one-object critical section avoids this
1992-
// because it keeps the deque locked across calls when it's already
1993-
// held, due to a fast-path optimization.
1994-
if (_PyObject_IsUniquelyReferenced((PyObject *)it)) {
1995-
Py_BEGIN_CRITICAL_SECTION(deque);
1996-
result = dequeiter_next_lock_held(it, deque);
1997-
Py_END_CRITICAL_SECTION();
1998-
return result;
1999-
}
2000-
#endif
2001-
20021999
Py_BEGIN_CRITICAL_SECTION2(it, deque);
20032000
result = dequeiter_next_lock_held(it, deque);
20042001
Py_END_CRITICAL_SECTION2();

0 commit comments

Comments
 (0)