Skip to content

Commit ee48c7d

Browse files
authored
bpo-43452: Micro-optimizations to PyType_Lookup (GH-24804)
The common case going through _PyType_Lookup is to have a cache hit. There are some small tweaks that can make this a little cheaper: * The name field identity is used for a cache hit and is kept alive by the cache. So there's no need to read the hash code o the name - instead, the address can be used as the hash. * There's no need to check if the name is cachable on the lookup either, it probably is, and if it is, it'll be in the cache. * If we clear the version tag when invalidating a type then we don't actually need to check for a valid version tag bit.
1 parent 2fd16ef commit ee48c7d

File tree

2 files changed

+24
-28
lines changed

2 files changed

+24
-28
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added micro-optimizations to ``_PyType_Lookup()`` to improve cache lookup performance in the common case of cache hits.

Objects/typeobject.c

Lines changed: 23 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,7 @@ class object "PyObject *" "&PyBaseObject_Type"
3232
& ((1 << MCACHE_SIZE_EXP) - 1))
3333

3434
#define MCACHE_HASH_METHOD(type, name) \
35-
MCACHE_HASH((type)->tp_version_tag, \
36-
((PyASCIIObject *)(name))->hash)
35+
MCACHE_HASH((type)->tp_version_tag, ((Py_ssize_t)(name)) >> 3)
3736
#define MCACHE_CACHEABLE_NAME(name) \
3837
PyUnicode_CheckExact(name) && \
3938
PyUnicode_IS_READY(name) && \
@@ -338,6 +337,7 @@ PyType_Modified(PyTypeObject *type)
338337
}
339338
}
340339
type->tp_flags &= ~Py_TPFLAGS_VALID_VERSION_TAG;
340+
type->tp_version_tag = 0; /* 0 is not a valid version tag */
341341
}
342342

343343
static void
@@ -396,6 +396,7 @@ type_mro_modified(PyTypeObject *type, PyObject *bases) {
396396
Py_XDECREF(type_mro_meth);
397397
type->tp_flags &= ~(Py_TPFLAGS_HAVE_VERSION_TAG|
398398
Py_TPFLAGS_VALID_VERSION_TAG);
399+
type->tp_version_tag = 0; /* 0 is not a valid version tag */
399400
}
400401

401402
static int
@@ -3351,18 +3352,15 @@ _PyType_Lookup(PyTypeObject *type, PyObject *name)
33513352
PyObject *res;
33523353
int error;
33533354

3354-
if (MCACHE_CACHEABLE_NAME(name) &&
3355-
_PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG)) {
3356-
/* fast path */
3357-
unsigned int h = MCACHE_HASH_METHOD(type, name);
3358-
struct type_cache *cache = get_type_cache();
3359-
struct type_cache_entry *entry = &cache->hashtable[h];
3360-
if (entry->version == type->tp_version_tag && entry->name == name) {
3355+
unsigned int h = MCACHE_HASH_METHOD(type, name);
3356+
struct type_cache *cache = get_type_cache();
3357+
struct type_cache_entry *entry = &cache->hashtable[h];
3358+
if (entry->version == type->tp_version_tag &&
3359+
entry->name == name) {
33613360
#if MCACHE_STATS
3362-
cache->hits++;
3361+
cache->hits++;
33633362
#endif
3364-
return entry->value;
3365-
}
3363+
return entry->value;
33663364
}
33673365

33683366
/* We may end up clearing live exceptions below, so make sure it's ours. */
@@ -3385,24 +3383,21 @@ _PyType_Lookup(PyTypeObject *type, PyObject *name)
33853383
return NULL;
33863384
}
33873385

3388-
if (MCACHE_CACHEABLE_NAME(name)) {
3389-
struct type_cache *cache = get_type_cache();
3390-
if (assign_version_tag(cache, type)) {
3391-
unsigned int h = MCACHE_HASH_METHOD(type, name);
3392-
struct type_cache_entry *entry = &cache->hashtable[h];
3393-
entry->version = type->tp_version_tag;
3394-
entry->value = res; /* borrowed */
3395-
assert(((PyASCIIObject *)(name))->hash != -1);
3386+
if (MCACHE_CACHEABLE_NAME(name) && assign_version_tag(cache, type)) {
3387+
h = MCACHE_HASH_METHOD(type, name);
3388+
struct type_cache_entry *entry = &cache->hashtable[h];
3389+
entry->version = type->tp_version_tag;
3390+
entry->value = res; /* borrowed */
3391+
assert(((PyASCIIObject *)(name))->hash != -1);
33963392
#if MCACHE_STATS
3397-
if (entry->name != Py_None && entry->name != name) {
3398-
cache->collisions++;
3399-
}
3400-
else {
3401-
cache->misses++;
3402-
}
3403-
#endif
3404-
Py_SETREF(entry->name, Py_NewRef(name));
3393+
if (entry->name != Py_None && entry->name != name) {
3394+
cache->collisions++;
34053395
}
3396+
else {
3397+
cache->misses++;
3398+
}
3399+
#endif
3400+
Py_SETREF(entry->name, Py_NewRef(name));
34063401
}
34073402
return res;
34083403
}

0 commit comments

Comments
 (0)