Skip to content

Commit 1d65711

Browse files
committed
Add type-local type cache
1 parent 04d3398 commit 1d65711

File tree

3 files changed

+75
-10
lines changed

3 files changed

+75
-10
lines changed

Include/cpython/object.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,9 @@ struct _specialization_cache {
259259
PyObject *getitem;
260260
uint32_t getitem_version;
261261
PyObject *init;
262+
#ifdef Py_GIL_DISABLED
263+
struct local_type_cache *local_type_cache;
264+
#endif
262265
};
263266

264267
/* The *real* layout of a type object when allocated on the heap */

Include/internal/pycore_typeobject.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,25 @@ struct type_cache {
7171
struct type_cache_entry hashtable[1 << MCACHE_SIZE_EXP];
7272
};
7373

74+
#ifdef Py_GIL_DISABLED
75+
76+
// Type attribute lookup cache which is type-specific. Only used
77+
// for heap types where we store a small additional cache in free-threaded
78+
// builds which can be accessed without any locking.
79+
#define LOCAL_CACHE_TYPE_SIZE 67
80+
81+
struct local_type_cache_entry {
82+
PyObject *name; // reference to exactly a str or NULL
83+
PyObject *value; // borrowed reference or NULL
84+
};
85+
86+
struct local_type_cache {
87+
unsigned int tp_version_tag;
88+
struct local_type_cache_entry entries[LOCAL_CACHE_TYPE_SIZE];
89+
};
90+
91+
#endif
92+
7493
typedef struct {
7594
PyTypeObject *type;
7695
int isbuiltin;

Objects/typeobject.c

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,6 +1021,22 @@ set_version_unlocked(PyTypeObject *tp, unsigned int version)
10211021
#endif
10221022
}
10231023

1024+
static void
1025+
clear_spec_cache(PyTypeObject *type)
1026+
{
1027+
if (PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) {
1028+
// This field *must* be invalidated if the type is modified (see the
1029+
// comment on struct _specialization_cache):
1030+
PyHeapTypeObject *heap_type = (PyHeapTypeObject *)type;
1031+
heap_type->_spec_cache.getitem = NULL;
1032+
struct local_type_cache *cache = heap_type->_spec_cache.local_type_cache;
1033+
if (cache != NULL) {
1034+
_PyMem_FreeDelayed(cache);
1035+
heap_type->_spec_cache.local_type_cache = NULL;
1036+
}
1037+
}
1038+
}
1039+
10241040
static void
10251041
type_modified_unlocked(PyTypeObject *type)
10261042
{
@@ -1082,11 +1098,7 @@ type_modified_unlocked(PyTypeObject *type)
10821098
}
10831099

10841100
set_version_unlocked(type, 0); /* 0 is not a valid version tag */
1085-
if (PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) {
1086-
// This field *must* be invalidated if the type is modified (see the
1087-
// comment on struct _specialization_cache):
1088-
((PyHeapTypeObject *)type)->_spec_cache.getitem = NULL;
1089-
}
1101+
clear_spec_cache(type);
10901102
}
10911103

10921104
void
@@ -1163,11 +1175,7 @@ type_mro_modified(PyTypeObject *type, PyObject *bases) {
11631175
assert(!(type->tp_flags & _Py_TPFLAGS_STATIC_BUILTIN));
11641176
set_version_unlocked(type, 0); /* 0 is not a valid version tag */
11651177
type->tp_versions_used = _Py_ATTR_CACHE_UNUSED;
1166-
if (PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) {
1167-
// This field *must* be invalidated if the type is modified (see the
1168-
// comment on struct _specialization_cache):
1169-
((PyHeapTypeObject *)type)->_spec_cache.getitem = NULL;
1170-
}
1178+
clear_spec_cache(type);
11711179
}
11721180

11731181
/*
@@ -5553,6 +5561,19 @@ _PyType_LookupRefAndVersion(PyTypeObject *type, PyObject *name, unsigned int *ve
55535561
struct type_cache *cache = get_type_cache();
55545562
struct type_cache_entry *entry = &cache->hashtable[h];
55555563
#ifdef Py_GIL_DISABLED
5564+
PyHeapTypeObject *heap_type = (PyHeapTypeObject *)type;
5565+
struct local_type_cache *local_cache = heap_type->_spec_cache.local_type_cache;
5566+
5567+
if (cache != NULL) {
5568+
Py_ssize_t index = (((Py_ssize_t)(name)) >> 3) % LOCAL_CACHE_TYPE_SIZE;
5569+
struct local_type_cache_entry *entry = &local_cache->entries[index];
5570+
PyObject *value;
5571+
if (entry->name == name && (value = _Py_TryXGetRef(&entry->value)) != NULL) {
5572+
*version = local_cache->tp_version_tag;
5573+
return value;
5574+
}
5575+
}
5576+
55565577
// synchronize-with other writing threads by doing an acquire load on the sequence
55575578
while (1) {
55585579
uint32_t sequence = _PySeqLock_BeginRead(&entry->sequence);
@@ -5638,6 +5659,28 @@ _PyType_LookupRefAndVersion(PyTypeObject *type, PyObject *name, unsigned int *ve
56385659
if (has_version) {
56395660
#if Py_GIL_DISABLED
56405661
update_cache_gil_disabled(entry, name, assigned_version, res);
5662+
if (res != NULL) {
5663+
if (PyType_HasFeature(type, Py_TPFLAGS_HEAPTYPE)) {
5664+
PyHeapTypeObject *heap_type = (PyHeapTypeObject *)type;
5665+
struct local_type_cache *local_cache = heap_type->_spec_cache.local_type_cache;
5666+
if (local_cache == NULL) {
5667+
BEGIN_TYPE_LOCK();
5668+
local_cache = PyMem_Calloc(1, sizeof(struct local_type_cache));
5669+
if (local_cache != NULL) {
5670+
local_cache->tp_version_tag = assigned_version;
5671+
heap_type->_spec_cache.local_type_cache = local_cache;
5672+
}
5673+
END_TYPE_LOCK();
5674+
}
5675+
if (local_cache != NULL) {
5676+
struct local_type_cache_entry *entry =
5677+
&local_cache->entries[(((Py_ssize_t)(name)) >> 3) % LOCAL_CACHE_TYPE_SIZE];
5678+
entry->name = name;
5679+
entry->value = res;
5680+
}
5681+
}
5682+
}
5683+
56415684
#else
56425685
PyObject *old_value = update_cache(entry, name, assigned_version, res);
56435686
Py_DECREF(old_value);

0 commit comments

Comments
 (0)