Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 52e902c

Browse filesBrowse files
authored
GH-109369: Add machinery for deoptimizing tier2 executors, both individually and globally. (GH-110384)
1 parent 32c37fe commit 52e902c
Copy full SHA for 52e902c

File tree

7 files changed

+353
-2
lines changed
Filter options

7 files changed

+353
-2
lines changed

‎Include/cpython/optimizer.h

Copy file name to clipboardExpand all lines: Include/cpython/optimizer.h
+26Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,27 @@
66
extern "C" {
77
#endif
88

9+
typedef struct _PyExecutorLinkListNode {
10+
struct _PyExecutorObject *next;
11+
struct _PyExecutorObject *previous;
12+
} _PyExecutorLinkListNode;
13+
14+
15+
/* Bloom filter with m = 256
16+
* https://en.wikipedia.org/wiki/Bloom_filter */
17+
#define BLOOM_FILTER_WORDS 8
18+
19+
typedef struct _bloom_filter {
20+
uint32_t bits[BLOOM_FILTER_WORDS];
21+
} _PyBloomFilter;
22+
923
typedef struct {
1024
uint8_t opcode;
1125
uint8_t oparg;
26+
uint8_t valid;
27+
uint8_t linked;
28+
_PyBloomFilter bloom;
29+
_PyExecutorLinkListNode links;
1230
} _PyVMData;
1331

1432
typedef struct _PyExecutorObject {
@@ -45,6 +63,14 @@ _PyOptimizer_BackEdge(struct _PyInterpreterFrame *frame, _Py_CODEUNIT *src, _Py_
4563

4664
extern _PyOptimizerObject _PyOptimizer_Default;
4765

66+
void _Py_ExecutorInit(_PyExecutorObject *, _PyBloomFilter *);
67+
void _Py_ExecutorClear(_PyExecutorObject *);
68+
void _Py_BloomFilter_Init(_PyBloomFilter *);
69+
void _Py_BloomFilter_Add(_PyBloomFilter *bloom, void *obj);
70+
PyAPI_FUNC(void) _Py_Executor_DependsOn(_PyExecutorObject *executor, void *obj);
71+
PyAPI_FUNC(void) _Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj);
72+
extern void _Py_Executors_InvalidateAll(PyInterpreterState *interp);
73+
4874
/* For testing */
4975
PyAPI_FUNC(PyObject *)PyUnstable_Optimizer_NewCounter(void);
5076
PyAPI_FUNC(PyObject *)PyUnstable_Optimizer_NewUOpOptimizer(void);

‎Include/internal/pycore_interp.h

Copy file name to clipboardExpand all lines: Include/internal/pycore_interp.h
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ struct _is {
215215
struct types_state types;
216216
struct callable_cache callable_cache;
217217
_PyOptimizerObject *optimizer;
218+
_PyExecutorObject *executor_list_head;
218219
uint16_t optimizer_resume_threshold;
219220
uint16_t optimizer_backedge_threshold;
220221
uint32_t next_func_version;

‎Lib/test/test_capi/test_misc.py

Copy file name to clipboardExpand all lines: Lib/test/test_capi/test_misc.py
+61Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2489,6 +2489,67 @@ def get_first_executor(func):
24892489
return None
24902490

24912491

2492+
class TestExecutorInvalidation(unittest.TestCase):
2493+
2494+
def setUp(self):
2495+
self.old = _testinternalcapi.get_optimizer()
2496+
self.opt = _testinternalcapi.get_counter_optimizer()
2497+
_testinternalcapi.set_optimizer(self.opt)
2498+
2499+
def tearDown(self):
2500+
_testinternalcapi.set_optimizer(self.old)
2501+
2502+
def test_invalidate_object(self):
2503+
# Generate a new set of functions at each call
2504+
ns = {}
2505+
func_src = "\n".join(
2506+
f"""
2507+
def f{n}():
2508+
for _ in range(1000):
2509+
pass
2510+
""" for n in range(5)
2511+
)
2512+
exec(textwrap.dedent(func_src), ns, ns)
2513+
funcs = [ ns[f'f{n}'] for n in range(5)]
2514+
objects = [object() for _ in range(5)]
2515+
2516+
for f in funcs:
2517+
f()
2518+
executors = [get_first_executor(f) for f in funcs]
2519+
# Set things up so each executor depends on the objects
2520+
# with an equal or lower index.
2521+
for i, exe in enumerate(executors):
2522+
self.assertTrue(exe.valid)
2523+
for obj in objects[:i+1]:
2524+
_testinternalcapi.add_executor_dependency(exe, obj)
2525+
self.assertTrue(exe.valid)
2526+
# Assert that the correct executors are invalidated
2527+
# and check that nothing crashes when we invalidate
2528+
# an executor mutliple times.
2529+
for i in (4,3,2,1,0):
2530+
_testinternalcapi.invalidate_executors(objects[i])
2531+
for exe in executors[i:]:
2532+
self.assertFalse(exe.valid)
2533+
for exe in executors[:i]:
2534+
self.assertTrue(exe.valid)
2535+
2536+
def test_uop_optimizer_invalidation(self):
2537+
# Generate a new function at each call
2538+
ns = {}
2539+
exec(textwrap.dedent("""
2540+
def f():
2541+
for i in range(1000):
2542+
pass
2543+
"""), ns, ns)
2544+
f = ns['f']
2545+
opt = _testinternalcapi.get_uop_optimizer()
2546+
with temporary_optimizer(opt):
2547+
f()
2548+
exe = get_first_executor(f)
2549+
self.assertTrue(exe.valid)
2550+
_testinternalcapi.invalidate_executors(f.__code__)
2551+
self.assertFalse(exe.valid)
2552+
24922553
class TestUops(unittest.TestCase):
24932554

24942555
def test_basic_loop(self):

‎Modules/_testinternalcapi.c

Copy file name to clipboardExpand all lines: Modules/_testinternalcapi.c
+28Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,6 +1002,32 @@ get_executor(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
10021002
return (PyObject *)PyUnstable_GetExecutor((PyCodeObject *)code, ioffset);
10031003
}
10041004

1005+
static PyObject *
1006+
add_executor_dependency(PyObject *self, PyObject *args)
1007+
{
1008+
PyObject *exec;
1009+
PyObject *obj;
1010+
if (!PyArg_ParseTuple(args, "OO", &exec, &obj)) {
1011+
return NULL;
1012+
}
1013+
/* No way to tell in general if exec is an executor, so we only accept
1014+
* counting_executor */
1015+
if (strcmp(Py_TYPE(exec)->tp_name, "counting_executor")) {
1016+
PyErr_SetString(PyExc_TypeError, "argument must be a counting_executor");
1017+
return NULL;
1018+
}
1019+
_Py_Executor_DependsOn((_PyExecutorObject *)exec, obj);
1020+
Py_RETURN_NONE;
1021+
}
1022+
1023+
static PyObject *
1024+
invalidate_executors(PyObject *self, PyObject *obj)
1025+
{
1026+
PyInterpreterState *interp = PyInterpreterState_Get();
1027+
_Py_Executors_InvalidateDependency(interp, obj);
1028+
Py_RETURN_NONE;
1029+
}
1030+
10051031
static int _pending_callback(void *arg)
10061032
{
10071033
/* we assume the argument is callable object to which we own a reference */
@@ -1565,6 +1591,8 @@ static PyMethodDef module_functions[] = {
15651591
{"get_executor", _PyCFunction_CAST(get_executor), METH_FASTCALL, NULL},
15661592
{"get_counter_optimizer", get_counter_optimizer, METH_NOARGS, NULL},
15671593
{"get_uop_optimizer", get_uop_optimizer, METH_NOARGS, NULL},
1594+
{"add_executor_dependency", add_executor_dependency, METH_VARARGS, NULL},
1595+
{"invalidate_executors", invalidate_executors, METH_O, NULL},
15681596
{"pending_threadfunc", _PyCFunction_CAST(pending_threadfunc),
15691597
METH_VARARGS | METH_KEYWORDS},
15701598
{"pending_identify", pending_identify, METH_VARARGS, NULL},

‎Python/instrumentation.c

Copy file name to clipboardExpand all lines: Python/instrumentation.c
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1582,6 +1582,7 @@ _Py_Instrument(PyCodeObject *code, PyInterpreterState *interp)
15821582
if (code->co_executors != NULL) {
15831583
_PyCode_Clear_Executors(code);
15841584
}
1585+
_Py_Executors_InvalidateDependency(interp, code);
15851586
int code_len = (int)Py_SIZE(code);
15861587
/* code->_co_firsttraceable >= code_len indicates
15871588
* that no instrumentation can be inserted.
@@ -1803,6 +1804,7 @@ _PyMonitoring_SetEvents(int tool_id, _PyMonitoringEventSet events)
18031804
return -1;
18041805
}
18051806
set_global_version(interp, new_version);
1807+
_Py_Executors_InvalidateAll(interp);
18061808
return instrument_all_executing_code_objects(interp);
18071809
}
18081810

@@ -1832,6 +1834,7 @@ _PyMonitoring_SetLocalEvents(PyCodeObject *code, int tool_id, _PyMonitoringEvent
18321834
/* Force instrumentation update */
18331835
code->_co_instrumentation_version -= MONITORING_VERSION_INCREMENT;
18341836
}
1837+
_Py_Executors_InvalidateDependency(interp, code);
18351838
if (_Py_Instrument(code, interp)) {
18361839
return -1;
18371840
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.