Skip to content

Commit 8144661

Browse files
authored
GH-113710: Fix updating of dict version tag and add watched dict stats (GH-115221)
1 parent 93ac78a commit 8144661

File tree

6 files changed

+22
-23
lines changed

6 files changed

+22
-23
lines changed

Include/cpython/pystats.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@ typedef struct _rare_event_stats {
133133
uint64_t builtin_dict;
134134
/* Modifying a function, e.g. func.__defaults__ = ..., etc. */
135135
uint64_t func_modification;
136+
/* Modifying a dict that is being watched */
137+
uint64_t watched_dict_modification;
138+
uint64_t watched_globals_modification;
136139
} RareEventStats;
137140

138141
typedef struct _stats {

Include/internal/pycore_dict.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ static inline PyDictUnicodeEntry* DK_UNICODE_ENTRIES(PyDictKeysObject *dk) {
209209

210210
#define DICT_VERSION_INCREMENT (1 << (DICT_MAX_WATCHERS + DICT_WATCHED_MUTATION_BITS))
211211
#define DICT_WATCHER_MASK ((1 << DICT_MAX_WATCHERS) - 1)
212+
#define DICT_WATCHER_AND_MODIFICATION_MASK ((1 << (DICT_MAX_WATCHERS + DICT_WATCHED_MUTATION_BITS)) - 1)
212213

213214
#ifdef Py_GIL_DISABLED
214215
#define DICT_NEXT_VERSION(INTERP) \
@@ -236,10 +237,10 @@ _PyDict_NotifyEvent(PyInterpreterState *interp,
236237
assert(Py_REFCNT((PyObject*)mp) > 0);
237238
int watcher_bits = mp->ma_version_tag & DICT_WATCHER_MASK;
238239
if (watcher_bits) {
240+
RARE_EVENT_STAT_INC(watched_dict_modification);
239241
_PyDict_SendEvent(watcher_bits, event, mp, key, value);
240-
return DICT_NEXT_VERSION(interp) | watcher_bits;
241242
}
242-
return DICT_NEXT_VERSION(interp);
243+
return DICT_NEXT_VERSION(interp) | (mp->ma_version_tag & DICT_WATCHER_AND_MODIFICATION_MASK);
243244
}
244245

245246
extern PyObject *_PyObject_MakeDictFromInstanceAttributes(PyObject *obj, PyDictValues *values);

Python/optimizer_analysis.c

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -28,25 +28,23 @@ increment_mutations(PyObject* dict) {
2828
d->ma_version_tag += (1 << DICT_MAX_WATCHERS);
2929
}
3030

31+
/* The first two dict watcher IDs are reserved for CPython,
32+
* so we don't need to check that they haven't been used */
33+
#define BUILTINS_WATCHER_ID 0
34+
#define GLOBALS_WATCHER_ID 1
35+
3136
static int
3237
globals_watcher_callback(PyDict_WatchEvent event, PyObject* dict,
3338
PyObject* key, PyObject* new_value)
3439
{
35-
if (event == PyDict_EVENT_CLONED) {
36-
return 0;
37-
}
38-
uint64_t watched_mutations = get_mutations(dict);
39-
if (watched_mutations < _Py_MAX_ALLOWED_GLOBALS_MODIFICATIONS) {
40-
_Py_Executors_InvalidateDependency(_PyInterpreterState_GET(), dict);
41-
increment_mutations(dict);
42-
}
43-
else {
44-
PyDict_Unwatch(1, dict);
45-
}
40+
RARE_EVENT_STAT_INC(watched_globals_modification);
41+
assert(get_mutations(dict) < _Py_MAX_ALLOWED_GLOBALS_MODIFICATIONS);
42+
_Py_Executors_InvalidateDependency(_PyInterpreterState_GET(), dict);
43+
increment_mutations(dict);
44+
PyDict_Unwatch(GLOBALS_WATCHER_ID, dict);
4645
return 0;
4746
}
4847

49-
5048
static void
5149
global_to_const(_PyUOpInstruction *inst, PyObject *obj)
5250
{
@@ -82,11 +80,6 @@ incorrect_keys(_PyUOpInstruction *inst, PyObject *obj)
8280
return 0;
8381
}
8482

85-
/* The first two dict watcher IDs are reserved for CPython,
86-
* so we don't need to check that they haven't been used */
87-
#define BUILTINS_WATCHER_ID 0
88-
#define GLOBALS_WATCHER_ID 1
89-
9083
/* Returns 1 if successfully optimized
9184
* 0 if the trace is not suitable for optimization (yet)
9285
* -1 if there was an error. */
@@ -117,8 +110,8 @@ remove_globals(_PyInterpreterFrame *frame, _PyUOpInstruction *buffer,
117110
uint32_t builtins_watched = 0;
118111
uint32_t globals_checked = 0;
119112
uint32_t globals_watched = 0;
120-
if (interp->dict_state.watchers[1] == NULL) {
121-
interp->dict_state.watchers[1] = globals_watcher_callback;
113+
if (interp->dict_state.watchers[GLOBALS_WATCHER_ID] == NULL) {
114+
interp->dict_state.watchers[GLOBALS_WATCHER_ID] = globals_watcher_callback;
122115
}
123116
for (int pc = 0; pc < buffer_size; pc++) {
124117
_PyUOpInstruction *inst = &buffer[pc];

Python/pylifecycle.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -611,7 +611,7 @@ static int
611611
builtins_dict_watcher(PyDict_WatchEvent event, PyObject *dict, PyObject *key, PyObject *new_value)
612612
{
613613
PyInterpreterState *interp = _PyInterpreterState_GET();
614-
if (event != PyDict_EVENT_CLONED && interp->rare_events.builtin_dict < _Py_MAX_ALLOWED_BUILTINS_MODIFICATIONS) {
614+
if (interp->rare_events.builtin_dict < _Py_MAX_ALLOWED_BUILTINS_MODIFICATIONS) {
615615
_Py_Executors_InvalidateAll(interp);
616616
}
617617
RARE_EVENT_INTERP_INC(interp, builtin_dict);

Python/specialize.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,8 @@ print_rare_event_stats(FILE *out, RareEventStats *stats)
275275
fprintf(out, "Rare event (set_eval_frame_func): %" PRIu64 "\n", stats->set_eval_frame_func);
276276
fprintf(out, "Rare event (builtin_dict): %" PRIu64 "\n", stats->builtin_dict);
277277
fprintf(out, "Rare event (func_modification): %" PRIu64 "\n", stats->func_modification);
278+
fprintf(out, "Rare event (watched_dict_modification): %" PRIu64 "\n", stats->watched_dict_modification);
279+
fprintf(out, "Rare event (watched_globals_modification): %" PRIu64 "\n", stats->watched_globals_modification);
278280
}
279281

280282
static void

Tools/scripts/summarize_stats.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,7 @@ def get_histogram(self, prefix: str) -> list[tuple[int, int]]:
415415
def get_rare_events(self) -> list[tuple[str, int]]:
416416
prefix = "Rare event "
417417
return [
418-
(key[len(prefix) + 1:-1], val)
418+
(key[len(prefix) + 1:-1].replace("_", " "), val)
419419
for key, val in self._data.items()
420420
if key.startswith(prefix)
421421
]

0 commit comments

Comments
 (0)