| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Apple Silicon has 128-byte cache lines. struct _qsbr_pad was padded to 64 bytes, so the QSBR states of two neighbouring threads shared a cache line and the unconditional store in _Py_qsbr_quiescent_state() (eval loop back-edges and calls) made threads that share no Python objects contend on it. Use _Py_QSBR_CACHE_LINE_SIZE (128 on AArch64, 64 elsewhere) for the padding, the array alignment and the tail padding of _PyThreadStateImpl added in pythongh-144438. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sorry, something went wrong.
| // Size used to pad and align the per-thread QSBR states (and the end of | ||
| // _PyThreadStateImpl) to avoid false sharing. Apple Silicon has 128-byte cache | ||
| // lines; with 64-byte padding the `seq` fields of two threads share a line and | ||
| // the stores in _Py_qsbr_quiescent_state() contend on every eval loop | ||
| // back-edge. Use 128 bytes for all AArch64 targets: on cores with 64-byte | ||
| // lines this only costs 64 bytes per thread. |
There was a problem hiding this comment.
| // Size used to pad and align the per-thread QSBR states (and the end of | |
| // _PyThreadStateImpl) to avoid false sharing. Apple Silicon has 128-byte cache | |
| // lines; with 64-byte padding the `seq` fields of two threads share a line and | |
| // the stores in _Py_qsbr_quiescent_state() contend on every eval loop | |
| // back-edge. Use 128 bytes for all AArch64 targets: on cores with 64-byte | |
| // lines this only costs 64 bytes per thread. | |
| // Exact cache line size for Apple Silicon should be 128. | |
| // $ sysctl -a hw | grep cachelinesize | |
| // hw.cachelinesize: 128 |
Sorry, something went wrong.
There was a problem hiding this comment.
Also looks good to me. Just to get super bikesheddy I appreciate some of what the initial comment said ("Size used to pad and align the per-thread QSBR states (and the end of _PyThreadStateImpl) to avoid false sharing. Apple Silicon has 128-byte cache lines" but also really like the "sysctl -a hw | grep cachelinesize" and associated output.
Sorry, something went wrong.
There was a problem hiding this comment.
awesome!
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
Adds _Py_QSBR_CACHE_LINE_SIZE (128 on __aarch64__ / _M_ARM64, 64 elsewhere) and uses it in the three places that hard-code 64:
The change is internal only (Py_BUILD_CORE): no ABI change, and other architectures are unchanged.
What goes wrong today
To check that the slot layout is the cause: an idle (detached) thread started after each worker keeps busy threads off neighbouring slots. On unpatched main that alone raises 14-thread scaling from ~6× to 7.9–8.6×.
Why gh-144438 didn't cover it
gh-144438 was found on x86, with 64-byte lines. The problem there was the first array entry sharing a line with some thread's tlbc_index (diagnosis). Its fix uses 64 for both the array alignment and the new tail padding. On x86 a 64-byte _qsbr_pad already gives each thread its own line, so the padding between entries never needed changing. On 128-byte lines it does, and the gh-144438 padding is only half a line. The layout is unchanged since 3.13 (3.13.16), so on a Mac the penalty is the same in every version. This fits why the gh-144438 regression was "not seen on Mac" (comment).
Results
M4 Pro (10P + 4E), macOS 26.7.1, Apple clang 17, ./configure --disable-gil, base main d42178a.
deepcopy and type_lookup are limited by something else.
Pure-Python loop per release and worker count; C stride testSpeedup over 1 worker (qsbr_false_sharing.py from the issue):
There is no difference with 2 threads (1.8–2.0× in every build); the penalty starts at 4.
qsbr_stride.c (the same store pattern in C), ns per iteration. The 14-thread column includes the E-cores.
Memory cost (AArch64 only)
Measured on a main build with lldb (sizeof, and mimalloc's mi_good_size(), which PyMem_RawCalloc uses in the free-threaded build):
That is about 64 B per QSBR slot. For comparison, CPython reserves a 16 MiB stack per thread on macOS.
Design notes
Tests
./python.exe -m test -j12 test_free_threading test_threading test_thread test_queue test_weakref test_gc test_sys test_capi test_concurrent_futures test_threading_local test_itertools test_generators: SUCCESS (79 files, 3256 tests).
🤖 Generated with Claude Code