Repository navigation
Description
Feature or enhancement
In the free-threaded build, advancing a collections.deque iterator enters a two-object critical section on the iterator and the deque (dequeiter_next, dequereviter_next, dequeiter_reduce), which normally acquires and releases both mutexes in the uncontended path.
Materializing a deque does this for every item. This covers list(d), [*d], lst.extend(d), and PySequence_Fast(d), and therefore also ''.join(d), which passes the deque's iterator to list.extend().
This repeated synchronization is costly on the tested Kunpeng 950 (AArch64, LSE). In a hot, uncontended 8-bit CAS-pair C loop, the measured CPU cost was about 10 ns per logical CAS on the 950 and 2.5 ns on an EPYC 9654; these figures include loop/accounting work and are not isolated instruction latencies. Mako's buffer is a deque joined with ''.join(), and its bm_mako render took about 2.26 times as long in the free-threaded build as in the default build on the 950.
Two independent improvements:
- Lock only the deque when advancing an iterator. All writes to the iterator's fields already happen under the deque's lock, so that lock alone serializes concurrent
next()calls on a shared iterator. - Copy a deque into a list under one critical section. Add a fast path in
list.extend()for deques and forward deque iterators, the same waylist.extend()already handles sets and dicts. Since the deque type lives in_collections, the module registers the fast path withlistobject.c.
Measured on 3.14.6, PGO+LTO, GCC 12.3.1, pinned to one core. Times are in µs for a deque of 1000 strings, on the Kunpeng 950:
| Operation | free-threaded before | free-threaded after | default before | default after |
|---|---|---|---|---|
''.join(d) |
54.8 | 9.4 | 8.8 | 5.9 |
list(d) |
50.3 | 5.1 | 4.7 | 1.8 |
for x in d: pass |
52.3 | 30.6 | 6.4 | 6.4 |
bm_mako (unmodified Mako 1.1.4) on the free-threaded build, before → after:
- Kunpeng 950: 19.09 → 12.75 ms (−33%)
- EPYC 9654: 10.15 → 9.21 ms (−9%)
The default build also gets slightly faster.
The proposed branches target CPython main. The measurements above are from a CPython 3.14.6 backport of both changes; they are not measurements of the main-branch port.
Has this already been discussed elsewhere?
No response given
Links to previous discussion of this feature:
No response
Activity
fix: store deque hooks per interpreter