Skip to content

Commit 8ebd944

Browse files
authored
Add extra stats for attribute misses (GH-26732)
1 parent 689a844 commit 8ebd944

File tree

2 files changed

+90
-2
lines changed

2 files changed

+90
-2
lines changed

Include/internal/pycore_code.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,8 @@ int _Py_Specialize_LoadAttr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name
325325
int _Py_Specialize_LoadGlobal(PyObject *globals, PyObject *builtins, _Py_CODEUNIT *instr, PyObject *name, SpecializedCacheEntry *cache);
326326

327327
#define SPECIALIZATION_STATS 0
328+
#define SPECIALIZATION_STATS_DETAILED 0
329+
328330
#if SPECIALIZATION_STATS
329331

330332
typedef struct _stats {
@@ -334,6 +336,9 @@ typedef struct _stats {
334336
uint64_t deferred;
335337
uint64_t miss;
336338
uint64_t deopt;
339+
#if SPECIALIZATION_STATS_DETAILED
340+
PyObject *miss_types;
341+
#endif
337342
} SpecializationStats;
338343

339344
extern SpecializationStats _specialization_stats[256];

Python/specialize.c

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include "Python.h"
33
#include "pycore_code.h"
44
#include "pycore_dict.h"
5+
#include "pycore_long.h"
56
#include "pycore_moduleobject.h"
67
#include "opcode.h"
78
#include "structmember.h" // struct PyMemberDef, T_OFFSET_EX
@@ -46,6 +47,24 @@ print_stats(SpecializationStats *stats, const char *name)
4647
PRINT_STAT(name, deferred);
4748
PRINT_STAT(name, miss);
4849
PRINT_STAT(name, deopt);
50+
#if SPECIALIZATION_STATS_DETAILED
51+
if (stats->miss_types == NULL) {
52+
return;
53+
}
54+
fprintf(stderr, " %s.fails:\n", name);
55+
PyObject *key, *count;
56+
Py_ssize_t pos = 0;
57+
while (PyDict_Next(stats->miss_types, &pos, &key, &count)) {
58+
PyObject *type = PyTuple_GetItem(key, 0);
59+
PyObject *name = PyTuple_GetItem(key, 1);
60+
PyObject *kind = PyTuple_GetItem(key, 2);
61+
fprintf(stderr, " %s.", ((PyTypeObject *)type)->tp_name);
62+
PyObject_Print(name, stderr, Py_PRINT_RAW);
63+
fprintf(stderr, " (");
64+
PyObject_Print(kind, stderr, Py_PRINT_RAW);
65+
fprintf(stderr, "): %ld\n", PyLong_AsLong(count));
66+
}
67+
#endif
4968
}
5069

5170
void
@@ -56,6 +75,57 @@ _Py_PrintSpecializationStats(void)
5675
print_stats(&_specialization_stats[LOAD_GLOBAL], "load_global");
5776
}
5877

78+
#if SPECIALIZATION_STATS_DETAILED
79+
void
80+
_Py_IncrementTypeCounter(int opcode, PyObject *type, PyObject *name, const char *kind)
81+
{
82+
PyObject *counter = _specialization_stats[opcode].miss_types;
83+
if (counter == NULL) {
84+
_specialization_stats[opcode].miss_types = PyDict_New();
85+
counter = _specialization_stats[opcode].miss_types;
86+
if (counter == NULL) {
87+
return;
88+
}
89+
}
90+
PyObject *key = NULL;
91+
PyObject *kind_object = _PyUnicode_FromASCII(kind, strlen(kind));
92+
if (kind_object == NULL) {
93+
PyErr_Clear();
94+
goto done;
95+
}
96+
key = PyTuple_Pack(3, type, name, kind_object);
97+
if (key == NULL) {
98+
PyErr_Clear();
99+
goto done;
100+
}
101+
PyObject *count = PyDict_GetItem(counter, key);
102+
if (count == NULL) {
103+
count = _PyLong_GetZero();
104+
if (PyDict_SetItem(counter, key, count) < 0) {
105+
PyErr_Clear();
106+
goto done;
107+
}
108+
}
109+
count = PyNumber_Add(count, _PyLong_GetOne());
110+
if (count == NULL) {
111+
PyErr_Clear();
112+
goto done;
113+
}
114+
if (PyDict_SetItem(counter, key, count)) {
115+
PyErr_Clear();
116+
}
117+
done:
118+
Py_XDECREF(kind_object);
119+
Py_XDECREF(key);
120+
}
121+
122+
#define SPECIALIZATION_FAIL(opcode, type, attribute, kind) _Py_IncrementTypeCounter(opcode, (PyObject *)(type), attribute, kind)
123+
124+
#endif
125+
#endif
126+
127+
#ifndef SPECIALIZATION_FAIL
128+
#define SPECIALIZATION_FAIL(opcode, type, attribute, kind) ((void)0)
59129
#endif
60130

61131
static SpecializedCacheOrInstruction *
@@ -243,28 +313,34 @@ specialize_module_load_attr(
243313
_Py_IDENTIFIER(__getattr__);
244314
PyDictObject *dict = (PyDictObject *)m->md_dict;
245315
if (dict == NULL) {
316+
SPECIALIZATION_FAIL(LOAD_ATTR, Py_TYPE(owner), name, "no __dict__");
246317
return -1;
247318
}
248319
if (dict->ma_keys->dk_kind != DICT_KEYS_UNICODE) {
320+
SPECIALIZATION_FAIL(LOAD_ATTR, Py_TYPE(owner), name, "non-string keys (or split)");
249321
return -1;
250322
}
251323
getattr = _PyUnicode_FromId(&PyId___getattr__); /* borrowed */
252324
if (getattr == NULL) {
325+
SPECIALIZATION_FAIL(LOAD_ATTR, Py_TYPE(owner), name, "module.__getattr__ overridden");
253326
PyErr_Clear();
254327
return -1;
255328
}
256329
Py_ssize_t index = _PyDict_GetItemHint(dict, getattr, -1, &value);
257330
assert(index != DKIX_ERROR);
258331
if (index != DKIX_EMPTY) {
332+
SPECIALIZATION_FAIL(LOAD_ATTR, Py_TYPE(owner), name, "module attribute not found");
259333
return -1;
260334
}
261335
index = _PyDict_GetItemHint(dict, name, -1, &value);
262336
assert (index != DKIX_ERROR);
263337
if (index != (uint16_t)index) {
338+
SPECIALIZATION_FAIL(LOAD_ATTR, Py_TYPE(owner), name, "index out of range");
264339
return -1;
265340
}
266341
uint32_t keys_version = _PyDictKeys_GetVersionForCurrentState(dict);
267342
if (keys_version == 0) {
343+
SPECIALIZATION_FAIL(LOAD_ATTR, Py_TYPE(owner), name, "no more key versions");
268344
return -1;
269345
}
270346
cache1->dk_version_or_hint = keys_version;
@@ -287,6 +363,7 @@ _Py_Specialize_LoadAttr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name, Sp
287363
}
288364
PyTypeObject *type = Py_TYPE(owner);
289365
if (type->tp_getattro != PyObject_GenericGetAttr) {
366+
SPECIALIZATION_FAIL(LOAD_ATTR, Py_TYPE(owner), name, "__getattribute__ overridden");
290367
goto fail;
291368
}
292369
if (type->tp_dict == NULL) {
@@ -299,17 +376,19 @@ _Py_Specialize_LoadAttr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name, Sp
299376
// We found an attribute with a data-like descriptor.
300377
PyTypeObject *dtype = Py_TYPE(descr);
301378
if (dtype != &PyMemberDescr_Type) {
379+
SPECIALIZATION_FAIL(LOAD_ATTR, Py_TYPE(owner), name, "not a member descriptor");
302380
goto fail;
303381
}
304382
// It's a slot
305383
PyMemberDescrObject *member = (PyMemberDescrObject *)descr;
306384
struct PyMemberDef *dmem = member->d_member;
307385
if (dmem->type != T_OBJECT_EX) {
308-
// It's a slot of a different type. We don't handle those.
386+
SPECIALIZATION_FAIL(LOAD_ATTR, Py_TYPE(owner), name, "non-object slot");
309387
goto fail;
310388
}
311389
Py_ssize_t offset = dmem->offset;
312390
if (offset != (uint16_t)offset) {
391+
SPECIALIZATION_FAIL(LOAD_ATTR, Py_TYPE(owner), name, "offset out of range");
313392
goto fail;
314393
}
315394
assert(offset > 0);
@@ -320,11 +399,12 @@ _Py_Specialize_LoadAttr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name, Sp
320399
}
321400
// No desciptor
322401
if (type->tp_dictoffset <= 0) {
323-
// No dictionary, or computed offset dictionary
402+
SPECIALIZATION_FAIL(LOAD_ATTR, Py_TYPE(owner), name, "no dict or negative offset");
324403
goto fail;
325404
}
326405
PyObject **dictptr = (PyObject **) ((char *)owner + type->tp_dictoffset);
327406
if (*dictptr == NULL || !PyDict_CheckExact(*dictptr)) {
407+
SPECIALIZATION_FAIL(LOAD_ATTR, Py_TYPE(owner), name, "no dict or not a dict");
328408
goto fail;
329409
}
330410
// We found an instance with a __dict__.
@@ -342,10 +422,12 @@ _Py_Specialize_LoadAttr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name, Sp
342422
Py_ssize_t index = _Py_dict_lookup(dict, name, hash, &value);
343423
assert (index != DKIX_ERROR);
344424
if (index != (uint16_t)index) {
425+
SPECIALIZATION_FAIL(LOAD_ATTR, Py_TYPE(owner), name, "index out of range");
345426
goto fail;
346427
}
347428
uint32_t keys_version = _PyDictKeys_GetVersionForCurrentState(dict);
348429
if (keys_version == 0) {
430+
SPECIALIZATION_FAIL(LOAD_ATTR, Py_TYPE(owner), name, "no more key versions");
349431
goto fail;
350432
}
351433
cache1->dk_version_or_hint = keys_version;
@@ -359,6 +441,7 @@ _Py_Specialize_LoadAttr(PyObject *owner, _Py_CODEUNIT *instr, PyObject *name, Sp
359441
Py_ssize_t hint =
360442
_PyDict_GetItemHint(dict, name, -1, &value);
361443
if (hint != (uint32_t)hint) {
444+
SPECIALIZATION_FAIL(LOAD_ATTR, Py_TYPE(owner), name, "hint out of range");
362445
goto fail;
363446
}
364447
cache1->dk_version_or_hint = (uint32_t)hint;

0 commit comments

Comments
 (0)