Skip to content

Commit 39bc958

Browse files
authored
gh-159860: Adjust QSBR cache line size from 64 to 128 on AArch64 (gh-159862)
1 parent 7436707 commit 39bc958

4 files changed

Lines changed: 24 additions & 5 deletions

File tree

‎Include/internal/pycore_qsbr.h‎

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,25 @@ struct _qsbr_thread_state {
6969
struct _qsbr_thread_state *freelist_next;
7070
};
7171

72+
// Size used to pad and align the per-thread QSBR states (and the end of
73+
// _PyThreadStateImpl) to avoid false sharing. Apple Silicon has 128-byte cache
74+
// lines:
75+
// $ sysctl -a hw | grep cachelinesize
76+
// hw.cachelinesize: 128
77+
// With 64-byte padding the `seq` fields of two threads share a line and the
78+
// stores in _Py_qsbr_quiescent_state() contend on every eval loop back-edge.
79+
// Use 128 bytes for all AArch64 targets: on cores with 64-byte lines this only
80+
// costs 64 bytes per thread.
81+
#if defined(__aarch64__) || defined(_M_ARM64)
82+
# define _Py_QSBR_CACHE_LINE_SIZE 128
83+
#else
84+
# define _Py_QSBR_CACHE_LINE_SIZE 64
85+
#endif
86+
7287
// Padding to avoid false sharing
7388
struct _qsbr_pad {
7489
struct _qsbr_thread_state qsbr;
75-
char __padding[64 - sizeof(struct _qsbr_thread_state)];
90+
char __padding[_Py_QSBR_CACHE_LINE_SIZE - sizeof(struct _qsbr_thread_state)];
7691
};
7792

7893
// Per-interpreter state
@@ -83,7 +98,7 @@ struct _qsbr_shared {
8398
// Minimum observed read sequence of all QSBR thread states
8499
uint64_t rd_seq;
85100

86-
// Array of QSBR thread states (aligned to 64 bytes).
101+
// Array of QSBR thread states (aligned to _Py_QSBR_CACHE_LINE_SIZE).
87102
struct _qsbr_pad *array;
88103
void *array_raw; // raw allocation pointer (for free)
89104
Py_ssize_t size;

‎Include/internal/pycore_tstate.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ typedef struct _PyThreadStateImpl {
109109
#ifdef Py_GIL_DISABLED
110110
// gh-144438: Add padding to ensure that the fields above don't share a
111111
// cache line with other allocations.
112-
char __padding[64];
112+
char __padding[_Py_QSBR_CACHE_LINE_SIZE];
113113
#endif
114114
} _PyThreadStateImpl;
115115

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix false sharing between the per-thread QSBR states in the
2+
:term:`free-threaded build` on AArch64 CPUs with 128-byte cache lines, such
3+
as Apple Silicon. CPU-bound threads that share no objects now scale like
4+
processes there.

‎Python/qsbr.c‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,10 @@ grow_thread_array(struct _qsbr_shared *shared)
8585
new_size = MIN_ARRAY_SIZE;
8686
}
8787

88-
// Overallocate by 63 bytes so we can align to a 64-byte boundary.
88+
// Overallocate so we can align to a cache line boundary.
8989
// This avoids potential false sharing between the first entry and other
9090
// allocations.
91-
size_t alignment = 64;
91+
size_t alignment = _Py_QSBR_CACHE_LINE_SIZE;
9292
size_t alloc_size = (size_t)new_size * sizeof(struct _qsbr_pad) + alignment - 1;
9393
void *raw = PyMem_RawCalloc(1, alloc_size);
9494
if (raw == NULL) {

0 commit comments

Comments
 (0)