Skip to content

Commit 235cacf

Browse files
authored
GH-114695: Add sys._clear_internal_caches (GH-115152)
1 parent 54bde5d commit 235cacf

File tree

12 files changed

+130
-84
lines changed

12 files changed

+130
-84
lines changed

Doc/library/sys.rst

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,17 @@ always available.
195195

196196
This function should be used for internal and specialized purposes only.
197197

198+
.. deprecated:: 3.13
199+
Use the more general :func:`_clear_internal_caches` function instead.
200+
201+
202+
.. function:: _clear_internal_caches()
203+
204+
Clear all internal performance-related caches. Use this function *only* to
205+
release unnecessary references and memory blocks when hunting for leaks.
206+
207+
.. versionadded:: 3.13
208+
198209

199210
.. function:: _current_frames()
200211

@@ -724,7 +735,7 @@ always available.
724735
regardless of their size. This function is mainly useful for tracking
725736
and debugging memory leaks. Because of the interpreter's internal
726737
caches, the result can vary from call to call; you may have to call
727-
:func:`_clear_type_cache()` and :func:`gc.collect()` to get more
738+
:func:`_clear_internal_caches()` and :func:`gc.collect()` to get more
728739
predictable results.
729740

730741
If a Python build or implementation cannot reasonably compute this

Include/cpython/optimizer.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,10 @@ typedef struct {
2424
uint8_t opcode;
2525
uint8_t oparg;
2626
uint8_t valid;
27-
uint8_t linked;
27+
int index; // Index of ENTER_EXECUTOR (if code isn't NULL, below).
2828
_PyBloomFilter bloom;
2929
_PyExecutorLinkListNode links;
30+
PyCodeObject *code; // Weak (NULL if no corresponding ENTER_EXECUTOR).
3031
} _PyVMData;
3132

3233
typedef struct {

Lib/test/libregrtest/refleak.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,8 +201,8 @@ def dash_R_cleanup(fs, ps, pic, zdc, abcs):
201201
# Clear caches
202202
clear_caches()
203203

204-
# Clear type cache at the end: previous function calls can modify types
205-
sys._clear_type_cache()
204+
# Clear other caches last (previous function calls can re-populate them):
205+
sys._clear_internal_caches()
206206

207207

208208
def warm_caches():

Lib/test/test_capi/test_opt.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import contextlib
22
import opcode
3+
import sys
34
import textwrap
45
import unittest
56

@@ -181,6 +182,21 @@ def f():
181182
_testinternalcapi.invalidate_executors(f.__code__)
182183
self.assertFalse(exe.is_valid())
183184

185+
def test_sys__clear_internal_caches(self):
186+
def f():
187+
for _ in range(1000):
188+
pass
189+
opt = _testinternalcapi.get_uop_optimizer()
190+
with temporary_optimizer(opt):
191+
f()
192+
exe = get_first_executor(f)
193+
self.assertIsNotNone(exe)
194+
self.assertTrue(exe.is_valid())
195+
sys._clear_internal_caches()
196+
self.assertFalse(exe.is_valid())
197+
exe = get_first_executor(f)
198+
self.assertIsNone(exe)
199+
184200
class TestUops(unittest.TestCase):
185201

186202
def test_basic_loop(self):

Lib/test/test_mailbox.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import tempfile
1111
from test import support
1212
from test.support import os_helper
13+
from test.support import refleak_helper
1314
from test.support import socket_helper
1415
import unittest
1516
import textwrap
@@ -2443,6 +2444,9 @@ def test__all__(self):
24432444

24442445
def tearDownModule():
24452446
support.reap_children()
2447+
# reap_children may have re-populated caches:
2448+
if refleak_helper.hunting_for_refleaks():
2449+
sys._clear_internal_caches()
24462450

24472451

24482452
if __name__ == '__main__':
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Add :func:`sys._clear_internal_caches`, which clears all internal
2+
performance-related caches (and deprecate the less-general
3+
:func:`sys._clear_type_cache` function).

Objects/codeobject.c

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1489,27 +1489,19 @@ PyCode_GetFreevars(PyCodeObject *code)
14891489
static void
14901490
clear_executors(PyCodeObject *co)
14911491
{
1492+
assert(co->co_executors);
14921493
for (int i = 0; i < co->co_executors->size; i++) {
1493-
Py_CLEAR(co->co_executors->executors[i]);
1494+
if (co->co_executors->executors[i]) {
1495+
_Py_ExecutorClear(co->co_executors->executors[i]);
1496+
}
14941497
}
14951498
PyMem_Free(co->co_executors);
14961499
co->co_executors = NULL;
14971500
}
14981501

14991502
void
1500-
_PyCode_Clear_Executors(PyCodeObject *code) {
1501-
int code_len = (int)Py_SIZE(code);
1502-
for (int i = 0; i < code_len; i += _PyInstruction_GetLength(code, i)) {
1503-
_Py_CODEUNIT *instr = &_PyCode_CODE(code)[i];
1504-
uint8_t opcode = instr->op.code;
1505-
uint8_t oparg = instr->op.arg;
1506-
if (opcode == ENTER_EXECUTOR) {
1507-
_PyExecutorObject *exec = code->co_executors->executors[oparg];
1508-
assert(exec->vm_data.opcode != ENTER_EXECUTOR);
1509-
instr->op.code = exec->vm_data.opcode;
1510-
instr->op.arg = exec->vm_data.oparg;
1511-
}
1512-
}
1503+
_PyCode_Clear_Executors(PyCodeObject *code)
1504+
{
15131505
clear_executors(code);
15141506
}
15151507

@@ -2360,10 +2352,10 @@ _PyCode_ConstantKey(PyObject *op)
23602352
void
23612353
_PyStaticCode_Fini(PyCodeObject *co)
23622354
{
2363-
deopt_code(co, _PyCode_CODE(co));
23642355
if (co->co_executors != NULL) {
23652356
clear_executors(co);
23662357
}
2358+
deopt_code(co, _PyCode_CODE(co));
23672359
PyMem_Free(co->co_extra);
23682360
if (co->_co_cached != NULL) {
23692361
Py_CLEAR(co->_co_cached->_co_code);

Python/bytecodes.c

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2370,23 +2370,12 @@ dummy_func(
23702370
CHECK_EVAL_BREAKER();
23712371

23722372
PyCodeObject *code = _PyFrame_GetCode(frame);
2373-
_PyExecutorObject *executor = code->co_executors->executors[oparg & 255];
2374-
if (executor->vm_data.valid) {
2375-
Py_INCREF(executor);
2376-
current_executor = executor;
2377-
GOTO_TIER_TWO();
2378-
}
2379-
else {
2380-
/* ENTER_EXECUTOR will be the first code unit of the instruction */
2381-
assert(oparg < 256);
2382-
code->co_executors->executors[oparg] = NULL;
2383-
opcode = this_instr->op.code = executor->vm_data.opcode;
2384-
this_instr->op.arg = executor->vm_data.oparg;
2385-
oparg = executor->vm_data.oparg;
2386-
Py_DECREF(executor);
2387-
next_instr = this_instr;
2388-
DISPATCH_GOTO();
2389-
}
2373+
current_executor = code->co_executors->executors[oparg & 255];
2374+
assert(current_executor->vm_data.index == INSTR_OFFSET() - 1);
2375+
assert(current_executor->vm_data.code == code);
2376+
assert(current_executor->vm_data.valid);
2377+
Py_INCREF(current_executor);
2378+
GOTO_TIER_TWO();
23902379
}
23912380

23922381
replaced op(_POP_JUMP_IF_FALSE, (cond -- )) {

Python/clinic/sysmodule.c.h

Lines changed: 19 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/generated_cases.c.h

Lines changed: 7 additions & 18 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Python/optimizer.c

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -73,25 +73,21 @@ insert_executor(PyCodeObject *code, _Py_CODEUNIT *instr, int index, _PyExecutorO
7373
Py_INCREF(executor);
7474
if (instr->op.code == ENTER_EXECUTOR) {
7575
assert(index == instr->op.arg);
76-
_PyExecutorObject *old = code->co_executors->executors[index];
77-
executor->vm_data.opcode = old->vm_data.opcode;
78-
executor->vm_data.oparg = old->vm_data.oparg;
79-
old->vm_data.opcode = 0;
80-
code->co_executors->executors[index] = executor;
81-
Py_DECREF(old);
76+
_Py_ExecutorClear(code->co_executors->executors[index]);
8277
}
8378
else {
8479
assert(code->co_executors->size == index);
8580
assert(code->co_executors->capacity > index);
86-
executor->vm_data.opcode = instr->op.code;
87-
executor->vm_data.oparg = instr->op.arg;
88-
code->co_executors->executors[index] = executor;
89-
assert(index < MAX_EXECUTORS_SIZE);
90-
instr->op.code = ENTER_EXECUTOR;
91-
instr->op.arg = index;
9281
code->co_executors->size++;
9382
}
94-
return;
83+
executor->vm_data.opcode = instr->op.code;
84+
executor->vm_data.oparg = instr->op.arg;
85+
executor->vm_data.code = code;
86+
executor->vm_data.index = (int)(instr - _PyCode_CODE(code));
87+
code->co_executors->executors[index] = executor;
88+
assert(index < MAX_EXECUTORS_SIZE);
89+
instr->op.code = ENTER_EXECUTOR;
90+
instr->op.arg = index;
9591
}
9692

9793
int
@@ -1071,15 +1067,15 @@ link_executor(_PyExecutorObject *executor)
10711067
}
10721068
head->vm_data.links.next = executor;
10731069
}
1074-
executor->vm_data.linked = true;
1070+
executor->vm_data.valid = true;
10751071
/* executor_list_head must be first in list */
10761072
assert(interp->executor_list_head->vm_data.links.previous == NULL);
10771073
}
10781074

10791075
static void
10801076
unlink_executor(_PyExecutorObject *executor)
10811077
{
1082-
if (!executor->vm_data.linked) {
1078+
if (!executor->vm_data.valid) {
10831079
return;
10841080
}
10851081
_PyExecutorLinkListNode *links = &executor->vm_data.links;
@@ -1097,7 +1093,7 @@ unlink_executor(_PyExecutorObject *executor)
10971093
assert(interp->executor_list_head == executor);
10981094
interp->executor_list_head = next;
10991095
}
1100-
executor->vm_data.linked = false;
1096+
executor->vm_data.valid = false;
11011097
}
11021098

11031099
/* This must be called by optimizers before using the executor */
@@ -1116,12 +1112,24 @@ void
11161112
_Py_ExecutorClear(_PyExecutorObject *executor)
11171113
{
11181114
unlink_executor(executor);
1115+
PyCodeObject *code = executor->vm_data.code;
1116+
if (code == NULL) {
1117+
return;
1118+
}
1119+
_Py_CODEUNIT *instruction = &_PyCode_CODE(code)[executor->vm_data.index];
1120+
assert(instruction->op.code == ENTER_EXECUTOR);
1121+
int index = instruction->op.arg;
1122+
assert(code->co_executors->executors[index] == executor);
1123+
instruction->op.code = executor->vm_data.opcode;
1124+
instruction->op.arg = executor->vm_data.oparg;
1125+
executor->vm_data.code = NULL;
1126+
Py_CLEAR(code->co_executors->executors[index]);
11191127
}
11201128

11211129
void
11221130
_Py_Executor_DependsOn(_PyExecutorObject *executor, void *obj)
11231131
{
1124-
assert(executor->vm_data.valid = true);
1132+
assert(executor->vm_data.valid);
11251133
_Py_BloomFilter_Add(&executor->vm_data.bloom, obj);
11261134
}
11271135

@@ -1140,8 +1148,7 @@ _Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj)
11401148
assert(exec->vm_data.valid);
11411149
_PyExecutorObject *next = exec->vm_data.links.next;
11421150
if (bloom_filter_may_contain(&exec->vm_data.bloom, &obj_filter)) {
1143-
exec->vm_data.valid = false;
1144-
unlink_executor(exec);
1151+
_Py_ExecutorClear(exec);
11451152
}
11461153
exec = next;
11471154
}
@@ -1151,15 +1158,14 @@ _Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj)
11511158
void
11521159
_Py_Executors_InvalidateAll(PyInterpreterState *interp)
11531160
{
1154-
/* Walk the list of executors */
1155-
for (_PyExecutorObject *exec = interp->executor_list_head; exec != NULL;) {
1156-
assert(exec->vm_data.valid);
1157-
_PyExecutorObject *next = exec->vm_data.links.next;
1158-
exec->vm_data.links.next = NULL;
1159-
exec->vm_data.links.previous = NULL;
1160-
exec->vm_data.valid = false;
1161-
exec->vm_data.linked = false;
1162-
exec = next;
1161+
while (interp->executor_list_head) {
1162+
_PyExecutorObject *executor = interp->executor_list_head;
1163+
if (executor->vm_data.code) {
1164+
// Clear the entire code object so its co_executors array be freed:
1165+
_PyCode_Clear_Executors(executor->vm_data.code);
1166+
}
1167+
else {
1168+
_Py_ExecutorClear(executor);
1169+
}
11631170
}
1164-
interp->executor_list_head = NULL;
11651171
}

Python/sysmodule.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2127,6 +2127,22 @@ sys__clear_type_cache_impl(PyObject *module)
21272127
Py_RETURN_NONE;
21282128
}
21292129

2130+
/*[clinic input]
2131+
sys._clear_internal_caches
2132+
2133+
Clear all internal performance-related caches.
2134+
[clinic start generated code]*/
2135+
2136+
static PyObject *
2137+
sys__clear_internal_caches_impl(PyObject *module)
2138+
/*[clinic end generated code: output=0ee128670a4966d6 input=253e741ca744f6e8]*/
2139+
{
2140+
PyInterpreterState *interp = _PyInterpreterState_GET();
2141+
_Py_Executors_InvalidateAll(interp);
2142+
PyType_ClearCache();
2143+
Py_RETURN_NONE;
2144+
}
2145+
21302146
/* Note that, for now, we do not have a per-interpreter equivalent
21312147
for sys.is_finalizing(). */
21322148

@@ -2461,6 +2477,7 @@ static PyMethodDef sys_methods[] = {
24612477
{"audit", _PyCFunction_CAST(sys_audit), METH_FASTCALL, audit_doc },
24622478
{"breakpointhook", _PyCFunction_CAST(sys_breakpointhook),
24632479
METH_FASTCALL | METH_KEYWORDS, breakpointhook_doc},
2480+
SYS__CLEAR_INTERNAL_CACHES_METHODDEF
24642481
SYS__CLEAR_TYPE_CACHE_METHODDEF
24652482
SYS__CURRENT_FRAMES_METHODDEF
24662483
SYS__CURRENT_EXCEPTIONS_METHODDEF

0 commit comments

Comments
 (0)