From 56734b37ff6ca4032c903a2960341426d6dd3410 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Wed, 14 Dec 2022 13:33:45 +0000 Subject: [PATCH 1/4] Add stats for the global type cache. --- Include/pystats.h | 2 ++ Objects/typeobject.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Include/pystats.h b/Include/pystats.h index 87e92aa4f05fa6..8ac4a7adcb6bbf 100644 --- a/Include/pystats.h +++ b/Include/pystats.h @@ -65,6 +65,8 @@ typedef struct _object_stats { uint64_t dict_materialized_new_key; uint64_t dict_materialized_too_big; uint64_t dict_materialized_str_subclass; + uint64_t type_cache_hits; + uint64_t type_cache_misses; } ObjectStats; typedef struct _stats { diff --git a/Objects/typeobject.c b/Objects/typeobject.c index ae80f5a8fd88e0..ac3967d16e4b2c 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -4164,8 +4164,10 @@ _PyType_Lookup(PyTypeObject *type, PyObject *name) cache->hits++; #endif assert(_PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG)); + OBJECT_STAT_INC(type_cache_hits); return entry->value; } + OBJECT_STAT_INC(type_cache_misses); /* We may end up clearing live exceptions below, so make sure it's ours. */ assert(!PyErr_Occurred()); From 0ceb725733e8ba839faaa3cf8d75e4f56c0b8ef5 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Wed, 14 Dec 2022 14:40:07 +0000 Subject: [PATCH 2/4] Print out stats --- Python/specialize.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Python/specialize.c b/Python/specialize.c index 678c5d66b660c4..d55c811e585651 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -187,6 +187,8 @@ print_object_stats(FILE *out, ObjectStats *stats) fprintf(out, "Object materialize dict (new key): %" PRIu64 "\n", stats->dict_materialized_new_key); fprintf(out, "Object materialize dict (too big): %" PRIu64 "\n", stats->dict_materialized_too_big); fprintf(out, "Object materialize dict (str subclass): %" PRIu64 "\n", stats->dict_materialized_str_subclass); + fprintf(out, "Object type cache hits: %" PRIu64 "\n", stats->type_cache_hits); + fprintf(out, "Object type cache misses: %" PRIu64 "\n", stats->type_cache_misses); } static void From 67fc05ffad61f745393385627bc17143ff3a3167 Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Wed, 14 Dec 2022 16:20:18 +0000 Subject: [PATCH 3/4] Add type cache stats for dunders and collisions. --- Include/internal/pycore_typeobject.h | 6 --- Include/pystats.h | 5 +++ Objects/typeobject.c | 66 ++++++++++------------------ Python/specialize.c | 3 ++ 4 files changed, 31 insertions(+), 49 deletions(-) diff --git a/Include/internal/pycore_typeobject.h b/Include/internal/pycore_typeobject.h index 71f3068900da31..c207ce615c6f91 100644 --- a/Include/internal/pycore_typeobject.h +++ b/Include/internal/pycore_typeobject.h @@ -36,15 +36,9 @@ struct type_cache_entry { }; #define MCACHE_SIZE_EXP 12 -#define MCACHE_STATS 0 struct type_cache { struct type_cache_entry hashtable[1 << MCACHE_SIZE_EXP]; -#if MCACHE_STATS - size_t hits; - size_t misses; - size_t collisions; -#endif }; /* For now we hard-code this to a value for which we are confident diff --git a/Include/pystats.h b/Include/pystats.h index 8ac4a7adcb6bbf..04630c9e0f92e5 100644 --- a/Include/pystats.h +++ b/Include/pystats.h @@ -67,8 +67,13 @@ typedef struct _object_stats { uint64_t dict_materialized_str_subclass; uint64_t type_cache_hits; uint64_t type_cache_misses; + uint64_t type_cache_dunder_hits; + uint64_t type_cache_dunder_misses; + uint64_t type_cache_collisions; } ObjectStats; +# + typedef struct _stats { OpcodeStats opcode_stats[256]; CallStats call_stats; diff --git a/Objects/typeobject.c b/Objects/typeobject.c index ac3967d16e4b2c..0370c1844aef78 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -327,18 +327,6 @@ static unsigned int _PyType_ClearCache(PyInterpreterState *interp) { struct type_cache *cache = &interp->types.type_cache; -#if MCACHE_STATS - size_t total = cache->hits + cache->collisions + cache->misses; - fprintf(stderr, "-- Method cache hits = %zd (%d%%)\n", - cache->hits, (int) (100.0 * cache->hits / total)); - fprintf(stderr, "-- Method cache true misses = %zd (%d%%)\n", - cache->misses, (int) (100.0 * cache->misses / total)); - fprintf(stderr, "-- Method cache collisions = %zd (%d%%)\n", - cache->collisions, (int) (100.0 * cache->collisions / total)); - fprintf(stderr, "-- Method cache size = %zd KiB\n", - sizeof(cache->hashtable) / 1024); -#endif - // Set to None, rather than NULL, so _PyType_Lookup() can // use Py_SETREF() rather than using slower Py_XSETREF(). type_cache_clear(cache, Py_None); @@ -4147,6 +4135,24 @@ find_name_in_mro(PyTypeObject *type, PyObject *name, int *error) return res; } +/* Check if the "readied" PyUnicode name + is a double-underscore special name. */ +static int +is_dunder_name(PyObject *name) +{ + Py_ssize_t length = PyUnicode_GET_LENGTH(name); + int kind = PyUnicode_KIND(name); + /* Special names contain at least "__x__" and are always ASCII. */ + if (length > 4 && kind == PyUnicode_1BYTE_KIND) { + const Py_UCS1 *characters = PyUnicode_1BYTE_DATA(name); + return ( + ((characters[length-2] == '_') && (characters[length-1] == '_')) && + ((characters[0] == '_') && (characters[1] == '_')) + ); + } + return 0; +} + /* Internal API to look for a name through the MRO. This returns a borrowed reference, and doesn't set an exception! */ PyObject * @@ -4160,14 +4166,13 @@ _PyType_Lookup(PyTypeObject *type, PyObject *name) struct type_cache_entry *entry = &cache->hashtable[h]; if (entry->version == type->tp_version_tag && entry->name == name) { -#if MCACHE_STATS - cache->hits++; -#endif assert(_PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG)); - OBJECT_STAT_INC(type_cache_hits); + OBJECT_STAT_INC_COND(type_cache_hits, !is_dunder_name(name)); + OBJECT_STAT_INC_COND(type_cache_dunder_hits, is_dunder_name(name)); return entry->value; } - OBJECT_STAT_INC(type_cache_misses); + OBJECT_STAT_INC_COND(type_cache_misses, !is_dunder_name(name)); + OBJECT_STAT_INC_COND(type_cache_dunder_misses, is_dunder_name(name)); /* We may end up clearing live exceptions below, so make sure it's ours. */ assert(!PyErr_Occurred()); @@ -4195,14 +4200,7 @@ _PyType_Lookup(PyTypeObject *type, PyObject *name) entry->version = type->tp_version_tag; entry->value = res; /* borrowed */ assert(_PyASCIIObject_CAST(name)->hash != -1); -#if MCACHE_STATS - if (entry->name != Py_None && entry->name != name) { - cache->collisions++; - } - else { - cache->misses++; - } -#endif + OBJECT_STAT_INC_COND(type_cache_collisions, entry->name != Py_None && entry->name != name); assert(_PyType_HasFeature(type, Py_TPFLAGS_VALID_VERSION_TAG)); Py_SETREF(entry->name, Py_NewRef(name)); } @@ -4219,24 +4217,6 @@ _PyType_LookupId(PyTypeObject *type, _Py_Identifier *name) return _PyType_Lookup(type, oname); } -/* Check if the "readied" PyUnicode name - is a double-underscore special name. */ -static int -is_dunder_name(PyObject *name) -{ - Py_ssize_t length = PyUnicode_GET_LENGTH(name); - int kind = PyUnicode_KIND(name); - /* Special names contain at least "__x__" and are always ASCII. */ - if (length > 4 && kind == PyUnicode_1BYTE_KIND) { - const Py_UCS1 *characters = PyUnicode_1BYTE_DATA(name); - return ( - ((characters[length-2] == '_') && (characters[length-1] == '_')) && - ((characters[0] == '_') && (characters[1] == '_')) - ); - } - return 0; -} - /* This is similar to PyObject_GenericGetAttr(), but uses _PyType_Lookup() instead of just looking in type->tp_dict. */ static PyObject * diff --git a/Python/specialize.c b/Python/specialize.c index d55c811e585651..755213e19a2163 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -189,6 +189,9 @@ print_object_stats(FILE *out, ObjectStats *stats) fprintf(out, "Object materialize dict (str subclass): %" PRIu64 "\n", stats->dict_materialized_str_subclass); fprintf(out, "Object type cache hits: %" PRIu64 "\n", stats->type_cache_hits); fprintf(out, "Object type cache misses: %" PRIu64 "\n", stats->type_cache_misses); + fprintf(out, "Object type cache collisions: %" PRIu64 "\n", stats->type_cache_collisions); + fprintf(out, "Object type cache dunder hits: %" PRIu64 "\n", stats->type_cache_dunder_hits); + fprintf(out, "Object type cache dunder misses: %" PRIu64 "\n", stats->type_cache_dunder_misses); } static void From 6d6aa01c377e0727f689a523d782cc3ada8be72d Mon Sep 17 00:00:00 2001 From: Mark Shannon Date: Wed, 14 Dec 2022 16:52:46 +0000 Subject: [PATCH 4/4] Rename 'type cache' to 'method cache' in stats output. --- Python/specialize.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Python/specialize.c b/Python/specialize.c index 755213e19a2163..73c0446743c774 100644 --- a/Python/specialize.c +++ b/Python/specialize.c @@ -187,11 +187,11 @@ print_object_stats(FILE *out, ObjectStats *stats) fprintf(out, "Object materialize dict (new key): %" PRIu64 "\n", stats->dict_materialized_new_key); fprintf(out, "Object materialize dict (too big): %" PRIu64 "\n", stats->dict_materialized_too_big); fprintf(out, "Object materialize dict (str subclass): %" PRIu64 "\n", stats->dict_materialized_str_subclass); - fprintf(out, "Object type cache hits: %" PRIu64 "\n", stats->type_cache_hits); - fprintf(out, "Object type cache misses: %" PRIu64 "\n", stats->type_cache_misses); - fprintf(out, "Object type cache collisions: %" PRIu64 "\n", stats->type_cache_collisions); - fprintf(out, "Object type cache dunder hits: %" PRIu64 "\n", stats->type_cache_dunder_hits); - fprintf(out, "Object type cache dunder misses: %" PRIu64 "\n", stats->type_cache_dunder_misses); + fprintf(out, "Object method cache hits: %" PRIu64 "\n", stats->type_cache_hits); + fprintf(out, "Object method cache misses: %" PRIu64 "\n", stats->type_cache_misses); + fprintf(out, "Object method cache collisions: %" PRIu64 "\n", stats->type_cache_collisions); + fprintf(out, "Object method cache dunder hits: %" PRIu64 "\n", stats->type_cache_dunder_hits); + fprintf(out, "Object method cache dunder misses: %" PRIu64 "\n", stats->type_cache_dunder_misses); } static void