Skip to content

Commit d3f79e5

Browse files
committed
Add optimization for non-interned type lookup.
1 parent e42bda9 commit d3f79e5

File tree

2 files changed

+59
-0
lines changed

2 files changed

+59
-0
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Add an optimized implementation of type lookup for names that are
2+
non-interned strings. This is only enabled for free-threaded builds since
3+
that kind of lookup can cause contention for the global type lock.

Objects/typeobject.c

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5728,6 +5728,50 @@ _PyTypes_AfterFork(void)
57285728
#endif
57295729
}
57305730

5731+
#ifdef Py_GIL_DISABLED
5732+
// Non-caching version of type lookup, containing a non-locking
5733+
// version of find_name_in_mro().
5734+
static unsigned int
5735+
type_lookup_non_interned(PyTypeObject *type, PyObject *name, _PyStackRef *out)
5736+
{
5737+
PyObject *mro = NULL;
5738+
Py_hash_t hash = _PyObject_HashFast(name);
5739+
if (hash == -1) {
5740+
goto error;
5741+
}
5742+
5743+
/* Keep a strong reference to mro because type->tp_mro can be replaced
5744+
during dict lookup, e.g. when comparing to non-string keys. */
5745+
mro = _PyType_GetMRO(type);
5746+
if (mro == NULL) {
5747+
assert(PyType_Ready(type));
5748+
goto error;
5749+
}
5750+
5751+
/* Look in tp_dict of types in MRO */
5752+
PyObject *res = NULL;
5753+
Py_ssize_t n = PyTuple_GET_SIZE(mro);
5754+
for (Py_ssize_t i = 0; i < n; i++) {
5755+
PyObject *base = PyTuple_GET_ITEM(mro, i);
5756+
PyObject *dict = lookup_tp_dict(_PyType_CAST(base));
5757+
assert(dict && PyDict_Check(dict));
5758+
if (_PyDict_GetItemRef_KnownHash((PyDictObject *)dict, name, hash, &res) < 0) {
5759+
goto error;
5760+
}
5761+
if (res != NULL) {
5762+
break;
5763+
}
5764+
}
5765+
Py_DECREF(mro);
5766+
*out = res ? PyStackRef_FromPyObjectSteal(res) : PyStackRef_NULL;
5767+
return 0;
5768+
error:
5769+
Py_XDECREF(mro);
5770+
*out = PyStackRef_NULL;
5771+
return 0;
5772+
}
5773+
#endif
5774+
57315775
/* Internal API to look for a name through the MRO.
57325776
This returns a strong reference, and doesn't set an exception!
57335777
If nonzero, version is set to the value of type->tp_version at the time of
@@ -5750,6 +5794,18 @@ _PyType_LookupRefAndVersion(PyTypeObject *type, PyObject *name, unsigned int *ve
57505794
unsigned int
57515795
_PyType_LookupStackRefAndVersion(PyTypeObject *type, PyObject *name, _PyStackRef *out)
57525796
{
5797+
#ifdef Py_GIL_DISABLED
5798+
// Bypass the type cache in this case since it is very unlikely it will do
5799+
// anything useful with a non-interned name lookup. This typically happens
5800+
// due to a getattr() call on a type with a name that has been constructed.
5801+
// We only have this path for the free-threaded build since cache misses are
5802+
// relatively more expensive for it and also to avoid contention on
5803+
// TYPE_LOCK. For the default build this extra branch is assumed to not be
5804+
// worth it, since this kind of lookup is quite rare.
5805+
if (!PyUnicode_CHECK_INTERNED(name) && _PyType_IsReady(type)) {
5806+
return type_lookup_non_interned(type, name, out);
5807+
}
5808+
#endif
57535809
unsigned int h = MCACHE_HASH_METHOD(type, name);
57545810
struct type_cache *cache = get_type_cache();
57555811
struct type_cache_entry *entry = &cache->hashtable[h];

0 commit comments

Comments
 (0)