Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Include/internal/pycore_qsbr.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,9 @@ struct _qsbr_shared {
// Minimum observed read sequence of all QSBR thread states
uint64_t rd_seq;

// Array of QSBR thread states.
// Array of QSBR thread states (aligned to 64 bytes).
struct _qsbr_pad *array;
void *array_raw; // raw allocation pointer (for free)
Py_ssize_t size;

// Freelist of unused _qsbr_thread_states (protected by mutex)
Expand Down
6 changes: 6 additions & 0 deletions Include/internal/pycore_tstate.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ typedef struct _PyThreadStateImpl {
#if _Py_TIER2
struct _PyJitTracerState *jit_tracer_state;
#endif

#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];
#endif
} _PyThreadStateImpl;

#ifdef __cplusplus
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Align the QSBR thread state array to a 64-byte cache line boundary to
avoid false sharing in the :term:`free-threaded build`.
30 changes: 21 additions & 9 deletions Objects/dictobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -3381,19 +3381,18 @@ dict_dealloc(PyObject *self)


static PyObject *
dict_repr_lock_held(PyObject *self)
anydict_repr_impl(PyObject *self)
{
PyDictObject *mp = (PyDictObject *)self;
PyObject *key = NULL, *value = NULL;
ASSERT_DICT_LOCKED(mp);

int res = Py_ReprEnter((PyObject *)mp);
int res = Py_ReprEnter(self);
if (res != 0) {
return (res > 0 ? PyUnicode_FromString("{...}") : NULL);
}

if (mp->ma_used == 0) {
Py_ReprLeave((PyObject *)mp);
Py_ReprLeave(self);
return PyUnicode_FromString("{}");
}

Expand All @@ -3412,7 +3411,7 @@ dict_repr_lock_held(PyObject *self)
Note that repr may mutate the dict. */
Py_ssize_t i = 0;
int first = 1;
while (_PyDict_Next((PyObject *)mp, &i, &key, &value, NULL)) {
while (_PyDict_Next(self, &i, &key, &value, NULL)) {
// Prevent repr from deleting key or value during key format.
Py_INCREF(key);
Py_INCREF(value);
Expand Down Expand Up @@ -3454,18 +3453,25 @@ dict_repr_lock_held(PyObject *self)
goto error;
}

Py_ReprLeave((PyObject *)mp);
Py_ReprLeave(self);

return PyUnicodeWriter_Finish(writer);

error:
Py_ReprLeave((PyObject *)mp);
Py_ReprLeave(self);
PyUnicodeWriter_Discard(writer);
Py_XDECREF(key);
Py_XDECREF(value);
return NULL;
}

static PyObject *
dict_repr_lock_held(PyObject *self)
{
ASSERT_DICT_LOCKED((PyDictObject *)self);
return anydict_repr_impl(self);
}

static PyObject *
dict_repr(PyObject *self)
{
Expand All @@ -3482,6 +3488,12 @@ dict_length(PyObject *self)
return GET_USED(_PyAnyDict_CAST(self));
}

static Py_ssize_t
frozendict_length(PyObject *self)
{
return _PyAnyDict_CAST(self)->ma_used;
}

static PyObject *
dict_subscript(PyObject *self, PyObject *key)
{
Expand Down Expand Up @@ -7833,7 +7845,7 @@ static PyNumberMethods frozendict_as_number = {
};

static PyMappingMethods frozendict_as_mapping = {
.mp_length = dict_length,
.mp_length = frozendict_length,
.mp_subscript = dict_subscript,
};

Expand All @@ -7856,7 +7868,7 @@ static PyMethodDef frozendict_methods[] = {
static PyObject *
frozendict_repr(PyObject *self)
{
PyObject *repr = dict_repr(self);
PyObject *repr = anydict_repr_impl(self);
if (repr == NULL) {
return NULL;
}
Expand Down
20 changes: 14 additions & 6 deletions Python/qsbr.c
Original file line number Diff line number Diff line change
Expand Up @@ -85,22 +85,29 @@ grow_thread_array(struct _qsbr_shared *shared)
new_size = MIN_ARRAY_SIZE;
}

struct _qsbr_pad *array = PyMem_RawCalloc(new_size, sizeof(*array));
if (array == NULL) {
// Overallocate by 63 bytes so we can align to a 64-byte boundary.
// This avoids potential false sharing between the first entry and other
// allocations.
size_t alignment = 64;
size_t alloc_size = (size_t)new_size * sizeof(struct _qsbr_pad) + alignment - 1;
void *raw = PyMem_RawCalloc(1, alloc_size);
if (raw == NULL) {
return -1;
}
struct _qsbr_pad *array = _Py_ALIGN_UP(raw, alignment);

struct _qsbr_pad *old = shared->array;
if (old != NULL) {
void *old_raw = shared->array_raw;
if (shared->array != NULL) {
memcpy(array, shared->array, shared->size * sizeof(*array));
}

shared->array = array;
shared->array_raw = raw;
shared->size = new_size;
shared->freelist = NULL;
initialize_new_array(shared);

PyMem_RawFree(old);
PyMem_RawFree(old_raw);
return 0;
}

Expand Down Expand Up @@ -257,8 +264,9 @@ void
_Py_qsbr_fini(PyInterpreterState *interp)
{
struct _qsbr_shared *shared = &interp->qsbr;
PyMem_RawFree(shared->array);
PyMem_RawFree(shared->array_raw);
shared->array = NULL;
shared->array_raw = NULL;
shared->size = 0;
shared->freelist = NULL;
}
Expand Down
Loading