https://github.com/python/cpython/commit/39bc958bf9beeb0eb5357d9d722a23ce7a6ce25d
commit: 39bc958bf9beeb0eb5357d9d722a23ce7a6ce25d
branch: main
author: Gr4ph0s <[email protected]>
committer: corona10 <[email protected]>
date: 2026-10-20T20:06:55Z
summary:
gh-159860: Adjust QSBR cache line size from 64 to 128 on AArch64 (gh-159862)
files:
A
Misc/NEWS.d/next/Core_and_Builtins/2026-10-19-21-30-00.gh-issue-159860.qsbr128.rst
M Include/internal/pycore_qsbr.h
M Include/internal/pycore_tstate.h
M Python/qsbr.c
diff --git a/Include/internal/pycore_qsbr.h b/Include/internal/pycore_qsbr.h
index eeca6fc472be37..91d3415614b154 100644
--- a/Include/internal/pycore_qsbr.h
+++ b/Include/internal/pycore_qsbr.h
@@ -69,10 +69,25 @@ struct _qsbr_thread_state {
struct _qsbr_thread_state *freelist_next;
};
+// 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:
+// $ sysctl -a hw | grep cachelinesize
+// hw.cachelinesize: 128
+// 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.
+#if defined(__aarch64__) || defined(_M_ARM64)
+# define _Py_QSBR_CACHE_LINE_SIZE 128
+#else
+# define _Py_QSBR_CACHE_LINE_SIZE 64
+#endif
+
// Padding to avoid false sharing
struct _qsbr_pad {
struct _qsbr_thread_state qsbr;
- char __padding[64 - sizeof(struct _qsbr_thread_state)];
+ char __padding[_Py_QSBR_CACHE_LINE_SIZE - sizeof(struct
_qsbr_thread_state)];
};
// Per-interpreter state
@@ -83,7 +98,7 @@ struct _qsbr_shared {
// Minimum observed read sequence of all QSBR thread states
uint64_t rd_seq;
- // Array of QSBR thread states (aligned to 64 bytes).
+ // Array of QSBR thread states (aligned to _Py_QSBR_CACHE_LINE_SIZE).
struct _qsbr_pad *array;
void *array_raw; // raw allocation pointer (for free)
Py_ssize_t size;
diff --git a/Include/internal/pycore_tstate.h b/Include/internal/pycore_tstate.h
index 58dd66b69efc9d..f1d8c4ba0d04e5 100644
--- a/Include/internal/pycore_tstate.h
+++ b/Include/internal/pycore_tstate.h
@@ -109,7 +109,7 @@ typedef struct _PyThreadStateImpl {
#ifdef Py_GIL_DISABLED
// gh-144438: Add padding to ensure that the fields above don't share a
// cache line with other allocations.
- char __padding[64];
+ char __padding[_Py_QSBR_CACHE_LINE_SIZE];
#endif
} _PyThreadStateImpl;
diff --git
a/Misc/NEWS.d/next/Core_and_Builtins/2026-10-19-21-30-00.gh-issue-159860.qsbr128.rst
b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-19-21-30-00.gh-issue-159860.qsbr128.rst
new file mode 100644
index 00000000000000..fc0d6fca4f7996
--- /dev/null
+++
b/Misc/NEWS.d/next/Core_and_Builtins/2026-10-19-21-30-00.gh-issue-159860.qsbr128.rst
@@ -0,0 +1,4 @@
+Fix false sharing between the per-thread QSBR states in the
+:term:`free-threaded build` on AArch64 CPUs with 128-byte cache lines, such
+as Apple Silicon. CPU-bound threads that share no objects now scale like
+processes there.
diff --git a/Python/qsbr.c b/Python/qsbr.c
index e9d935bfb40d84..a50d66e3e9e7ae 100644
--- a/Python/qsbr.c
+++ b/Python/qsbr.c
@@ -85,10 +85,10 @@ grow_thread_array(struct _qsbr_shared *shared)
new_size = MIN_ARRAY_SIZE;
}
- // Overallocate by 63 bytes so we can align to a 64-byte boundary.
+ // Overallocate so we can align to a cache line boundary.
// This avoids potential false sharing between the first entry and other
// allocations.
- size_t alignment = 64;
+ size_t alignment = _Py_QSBR_CACHE_LINE_SIZE;
size_t alloc_size = (size_t)new_size * sizeof(struct _qsbr_pad) +
alignment - 1;
void *raw = PyMem_RawCalloc(1, alloc_size);
if (raw == NULL) {
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]