Skip to content

Commit b6bca55

Browse files
committed
Use unicode hash/compare for mcache.
This allows cache to work with non-interned strings.
1 parent 8679c8d commit b6bca55

File tree

1 file changed

+42
-9
lines changed

1 file changed

+42
-9
lines changed

Objects/typeobject.c

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,46 @@ class object "PyObject *" "&PyBaseObject_Type"
4343
MCACHE_MAX_ATTR_SIZE, since it might be a problem if very large
4444
strings are used as attribute names. */
4545
#define MCACHE_MAX_ATTR_SIZE 100
46-
#define MCACHE_HASH(version, name_hash) \
47-
(((unsigned int)(version) ^ (unsigned int)(name_hash)) \
48-
& ((1 << MCACHE_SIZE_EXP) - 1))
4946

50-
#define MCACHE_HASH_METHOD(type, name) \
51-
MCACHE_HASH(FT_ATOMIC_LOAD_UINT32_RELAXED((type)->tp_version_tag), \
52-
((Py_ssize_t)(name)) >> 3)
47+
static inline unsigned int
48+
mcache_name_hash(PyTypeObject *type, PyObject *name)
49+
{
50+
unsigned int name_hash;
51+
#if Py_GIL_DISABLED
52+
// Cache misses are relatively more expensive for the free-threaded build.
53+
// So we use the unicode string hash and unicode compare for caching
54+
// names. This allows caching of non-interned strings.
55+
if (PyUnicode_CheckExact(name)) {
56+
name_hash = (unsigned int)_PyObject_HashFast(name);
57+
}
58+
else {
59+
name_hash = 0;
60+
}
61+
#else
62+
// Use the pointer value of the string for the hash and the compare. This
63+
// is faster but non-interned strings can't use the cache.
64+
name_hash = ((Py_ssize_t)(name)) >> 3;
65+
#endif
66+
unsigned int version = FT_ATOMIC_LOAD_UINT32_RELAXED((type)->tp_version_tag);
67+
return (((unsigned int)(version) ^ (unsigned int)(name_hash)) &
68+
((1 << MCACHE_SIZE_EXP) - 1));
69+
}
70+
71+
static inline int
72+
mcache_name_eq(PyObject *entry_name, PyObject *name)
73+
{
74+
#ifdef Py_GIL_DISABLED
75+
if (entry_name == NULL || entry_name == Py_None) {
76+
return 0;
77+
}
78+
assert(PyUnicode_CheckExact(entry_name));
79+
assert(PyUnicode_Check(name));
80+
return _PyUnicode_Equal(entry_name, name);
81+
#else
82+
return entry_name == name;
83+
#endif
84+
}
85+
5386
#define MCACHE_CACHEABLE_NAME(name) \
5487
PyUnicode_CheckExact(name) && \
5588
(PyUnicode_GET_LENGTH(name) <= MCACHE_MAX_ATTR_SIZE)
@@ -5721,7 +5754,7 @@ _PyType_LookupRefAndVersion(PyTypeObject *type, PyObject *name, unsigned int *ve
57215754
unsigned int
57225755
_PyType_LookupStackRefAndVersion(PyTypeObject *type, PyObject *name, _PyStackRef *out)
57235756
{
5724-
unsigned int h = MCACHE_HASH_METHOD(type, name);
5757+
unsigned int h = mcache_name_hash(type, name);
57255758
struct type_cache *cache = get_type_cache();
57265759
struct type_cache_entry *entry = &cache->hashtable[h];
57275760
#ifdef Py_GIL_DISABLED
@@ -5731,7 +5764,7 @@ _PyType_LookupStackRefAndVersion(PyTypeObject *type, PyObject *name, _PyStackRef
57315764
uint32_t entry_version = _Py_atomic_load_uint32_acquire(&entry->version);
57325765
uint32_t type_version = _Py_atomic_load_uint32_acquire(&type->tp_version_tag);
57335766
if (entry_version == type_version &&
5734-
_Py_atomic_load_ptr_relaxed(&entry->name) == name) {
5767+
mcache_name_eq(_Py_atomic_load_ptr_relaxed(&entry->name), name)) {
57355768
OBJECT_STAT_INC_COND(type_cache_hits, !is_dunder_name(name));
57365769
OBJECT_STAT_INC_COND(type_cache_dunder_hits, is_dunder_name(name));
57375770
if (_Py_TryXGetStackRef(&entry->value, out)) {
@@ -5752,7 +5785,7 @@ _PyType_LookupStackRefAndVersion(PyTypeObject *type, PyObject *name, _PyStackRef
57525785
}
57535786
}
57545787
#else
5755-
if (entry->version == type->tp_version_tag && entry->name == name) {
5788+
if (entry->version == type->tp_version_tag && mcache_name_eq(entry->name, name)) {
57565789
assert(type->tp_version_tag);
57575790
OBJECT_STAT_INC_COND(type_cache_hits, !is_dunder_name(name));
57585791
OBJECT_STAT_INC_COND(type_cache_dunder_hits, is_dunder_name(name));

0 commit comments

Comments
 (0)