mirror of
https://github.com/python/cpython.git
synced 2026-04-13 23:31:02 +00:00
[3.14] gh-145685: Improve scaling of type attribute lookups (gh-145774) (#145874)
Avoid locking in the PyType_Lookup cache-miss path if the type's
tp_version_tag is already valid.
(cherry picked from commit cd52172831)
Co-authored-by: Sam Gross <colesbury@gmail.com>
This commit is contained in:
parent
6669b20514
commit
cedff2d617
3 changed files with 36 additions and 27 deletions
|
|
@ -87,6 +87,8 @@ extern "C" {
|
|||
_Py_atomic_store_int_relaxed(&value, new_value)
|
||||
#define FT_ATOMIC_LOAD_INT_RELAXED(value) \
|
||||
_Py_atomic_load_int_relaxed(&value)
|
||||
#define FT_ATOMIC_LOAD_UINT(value) \
|
||||
_Py_atomic_load_uint(&value)
|
||||
#define FT_ATOMIC_STORE_UINT_RELAXED(value, new_value) \
|
||||
_Py_atomic_store_uint_relaxed(&value, new_value)
|
||||
#define FT_ATOMIC_LOAD_UINT_RELAXED(value) \
|
||||
|
|
@ -158,6 +160,7 @@ extern "C" {
|
|||
#define FT_ATOMIC_STORE_INT(value, new_value) value = new_value
|
||||
#define FT_ATOMIC_LOAD_INT_RELAXED(value) value
|
||||
#define FT_ATOMIC_STORE_INT_RELAXED(value, new_value) value = new_value
|
||||
#define FT_ATOMIC_LOAD_UINT(value) value
|
||||
#define FT_ATOMIC_LOAD_UINT_RELAXED(value) value
|
||||
#define FT_ATOMIC_STORE_UINT_RELAXED(value, new_value) value = new_value
|
||||
#define FT_ATOMIC_LOAD_LONG_RELAXED(value) value
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
Improve scaling of type attribute lookups in the :term:`free-threaded build` by
|
||||
avoiding contention on the internal type lock.
|
||||
|
|
@ -51,8 +51,8 @@ class object "PyObject *" "&PyBaseObject_Type"
|
|||
MCACHE_HASH(FT_ATOMIC_LOAD_UINT32_RELAXED((type)->tp_version_tag), \
|
||||
((Py_ssize_t)(name)) >> 3)
|
||||
#define MCACHE_CACHEABLE_NAME(name) \
|
||||
PyUnicode_CheckExact(name) && \
|
||||
(PyUnicode_GET_LENGTH(name) <= MCACHE_MAX_ATTR_SIZE)
|
||||
(PyUnicode_CheckExact(name) && \
|
||||
(PyUnicode_GET_LENGTH(name) <= MCACHE_MAX_ATTR_SIZE))
|
||||
|
||||
#define NEXT_VERSION_TAG(interp) \
|
||||
(interp)->types.next_version_tag
|
||||
|
|
@ -5708,8 +5708,6 @@ PyObject_GetItemData(PyObject *obj)
|
|||
static int
|
||||
find_name_in_mro(PyTypeObject *type, PyObject *name, _PyStackRef *out)
|
||||
{
|
||||
ASSERT_TYPE_LOCK_HELD();
|
||||
|
||||
Py_hash_t hash = _PyObject_HashFast(name);
|
||||
if (hash == -1) {
|
||||
PyErr_Clear();
|
||||
|
|
@ -5860,6 +5858,14 @@ _PyType_LookupRefAndVersion(PyTypeObject *type, PyObject *name, unsigned int *ve
|
|||
return PyStackRef_AsPyObjectSteal(out);
|
||||
}
|
||||
|
||||
static int
|
||||
should_assign_version_tag(PyTypeObject *type, PyObject *name, unsigned int version_tag)
|
||||
{
|
||||
return (version_tag == 0
|
||||
&& FT_ATOMIC_LOAD_UINT16_RELAXED(type->tp_versions_used) < MAX_VERSIONS_PER_CLASS
|
||||
&& MCACHE_CACHEABLE_NAME(name));
|
||||
}
|
||||
|
||||
unsigned int
|
||||
_PyType_LookupStackRefAndVersion(PyTypeObject *type, PyObject *name, _PyStackRef *out)
|
||||
{
|
||||
|
|
@ -5908,24 +5914,20 @@ _PyType_LookupStackRefAndVersion(PyTypeObject *type, PyObject *name, _PyStackRef
|
|||
/* We may end up clearing live exceptions below, so make sure it's ours. */
|
||||
assert(!PyErr_Occurred());
|
||||
|
||||
// We need to atomically do the lookup and capture the version before
|
||||
// anyone else can modify our mro or mutate the type.
|
||||
|
||||
int res;
|
||||
PyInterpreterState *interp = _PyInterpreterState_GET();
|
||||
int has_version = 0;
|
||||
unsigned int assigned_version = 0;
|
||||
|
||||
BEGIN_TYPE_LOCK();
|
||||
// We must assign the version before doing the lookup. If
|
||||
// find_name_in_mro() blocks and releases the critical section
|
||||
// then the type version can change.
|
||||
if (MCACHE_CACHEABLE_NAME(name)) {
|
||||
has_version = assign_version_tag(interp, type);
|
||||
assigned_version = type->tp_version_tag;
|
||||
unsigned int version_tag = FT_ATOMIC_LOAD_UINT(type->tp_version_tag);
|
||||
if (should_assign_version_tag(type, name, version_tag)) {
|
||||
BEGIN_TYPE_LOCK();
|
||||
assign_version_tag(interp, type);
|
||||
version_tag = type->tp_version_tag;
|
||||
res = find_name_in_mro(type, name, out);
|
||||
END_TYPE_LOCK();
|
||||
}
|
||||
else {
|
||||
res = find_name_in_mro(type, name, out);
|
||||
}
|
||||
res = find_name_in_mro(type, name, out);
|
||||
END_TYPE_LOCK();
|
||||
|
||||
/* Only put NULL results into cache if there was no error. */
|
||||
if (res < 0) {
|
||||
|
|
@ -5933,16 +5935,18 @@ _PyType_LookupStackRefAndVersion(PyTypeObject *type, PyObject *name, _PyStackRef
|
|||
return 0;
|
||||
}
|
||||
|
||||
if (has_version) {
|
||||
PyObject *res_obj = PyStackRef_AsPyObjectBorrow(*out);
|
||||
#if Py_GIL_DISABLED
|
||||
update_cache_gil_disabled(entry, name, assigned_version, res_obj);
|
||||
#else
|
||||
PyObject *old_value = update_cache(entry, name, assigned_version, res_obj);
|
||||
Py_DECREF(old_value);
|
||||
#endif
|
||||
if (version_tag == 0 || !MCACHE_CACHEABLE_NAME(name)) {
|
||||
return 0;
|
||||
}
|
||||
return has_version ? assigned_version : 0;
|
||||
|
||||
PyObject *res_obj = PyStackRef_AsPyObjectBorrow(*out);
|
||||
#if Py_GIL_DISABLED
|
||||
update_cache_gil_disabled(entry, name, version_tag, res_obj);
|
||||
#else
|
||||
PyObject *old_value = update_cache(entry, name, version_tag, res_obj);
|
||||
Py_DECREF(old_value);
|
||||
#endif
|
||||
return version_tag;
|
||||
}
|
||||
|
||||
/* Internal API to look for a name through the MRO.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue