Skip to content

Commit

Permalink
GH-114695: Add sys._clear_internal_caches (GH-115152)
Browse files Browse the repository at this point in the history
  • Loading branch information
brandtbucher authored Feb 12, 2024
1 parent 54bde5d commit 235cacf
Show file tree
Hide file tree
Showing 12 changed files with 130 additions and 84 deletions.
13 changes: 12 additions & 1 deletion Doc/library/sys.rst
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,17 @@ always available.

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

.. deprecated:: 3.13
Use the more general :func:`_clear_internal_caches` function instead.


.. function:: _clear_internal_caches()

Clear all internal performance-related caches. Use this function *only* to
release unnecessary references and memory blocks when hunting for leaks.

.. versionadded:: 3.13


.. function:: _current_frames()

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

If a Python build or implementation cannot reasonably compute this
Expand Down
3 changes: 2 additions & 1 deletion Include/cpython/optimizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,10 @@ typedef struct {
uint8_t opcode;
uint8_t oparg;
uint8_t valid;
uint8_t linked;
int index; // Index of ENTER_EXECUTOR (if code isn't NULL, below).
_PyBloomFilter bloom;
_PyExecutorLinkListNode links;
PyCodeObject *code; // Weak (NULL if no corresponding ENTER_EXECUTOR).
} _PyVMData;

typedef struct {
Expand Down
4 changes: 2 additions & 2 deletions Lib/test/libregrtest/refleak.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,8 @@ def dash_R_cleanup(fs, ps, pic, zdc, abcs):
# Clear caches
clear_caches()

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


def warm_caches():
Expand Down
16 changes: 16 additions & 0 deletions Lib/test/test_capi/test_opt.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import contextlib
import opcode
import sys
import textwrap
import unittest

Expand Down Expand Up @@ -181,6 +182,21 @@ def f():
_testinternalcapi.invalidate_executors(f.__code__)
self.assertFalse(exe.is_valid())

def test_sys__clear_internal_caches(self):
def f():
for _ in range(1000):
pass
opt = _testinternalcapi.get_uop_optimizer()
with temporary_optimizer(opt):
f()
exe = get_first_executor(f)
self.assertIsNotNone(exe)
self.assertTrue(exe.is_valid())
sys._clear_internal_caches()
self.assertFalse(exe.is_valid())
exe = get_first_executor(f)
self.assertIsNone(exe)

class TestUops(unittest.TestCase):

def test_basic_loop(self):
Expand Down
4 changes: 4 additions & 0 deletions Lib/test/test_mailbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import tempfile
from test import support
from test.support import os_helper
from test.support import refleak_helper
from test.support import socket_helper
import unittest
import textwrap
Expand Down Expand Up @@ -2443,6 +2444,9 @@ def test__all__(self):

def tearDownModule():
support.reap_children()
# reap_children may have re-populated caches:
if refleak_helper.hunting_for_refleaks():
sys._clear_internal_caches()


if __name__ == '__main__':
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add :func:`sys._clear_internal_caches`, which clears all internal
performance-related caches (and deprecate the less-general
:func:`sys._clear_type_cache` function).
22 changes: 7 additions & 15 deletions Objects/codeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -1489,27 +1489,19 @@ PyCode_GetFreevars(PyCodeObject *code)
static void
clear_executors(PyCodeObject *co)
{
assert(co->co_executors);
for (int i = 0; i < co->co_executors->size; i++) {
Py_CLEAR(co->co_executors->executors[i]);
if (co->co_executors->executors[i]) {
_Py_ExecutorClear(co->co_executors->executors[i]);
}
}
PyMem_Free(co->co_executors);
co->co_executors = NULL;
}

void
_PyCode_Clear_Executors(PyCodeObject *code) {
int code_len = (int)Py_SIZE(code);
for (int i = 0; i < code_len; i += _PyInstruction_GetLength(code, i)) {
_Py_CODEUNIT *instr = &_PyCode_CODE(code)[i];
uint8_t opcode = instr->op.code;
uint8_t oparg = instr->op.arg;
if (opcode == ENTER_EXECUTOR) {
_PyExecutorObject *exec = code->co_executors->executors[oparg];
assert(exec->vm_data.opcode != ENTER_EXECUTOR);
instr->op.code = exec->vm_data.opcode;
instr->op.arg = exec->vm_data.oparg;
}
}
_PyCode_Clear_Executors(PyCodeObject *code)
{
clear_executors(code);
}

Expand Down Expand Up @@ -2360,10 +2352,10 @@ _PyCode_ConstantKey(PyObject *op)
void
_PyStaticCode_Fini(PyCodeObject *co)
{
deopt_code(co, _PyCode_CODE(co));
if (co->co_executors != NULL) {
clear_executors(co);
}
deopt_code(co, _PyCode_CODE(co));
PyMem_Free(co->co_extra);
if (co->_co_cached != NULL) {
Py_CLEAR(co->_co_cached->_co_code);
Expand Down
23 changes: 6 additions & 17 deletions Python/bytecodes.c
Original file line number Diff line number Diff line change
Expand Up @@ -2370,23 +2370,12 @@ dummy_func(
CHECK_EVAL_BREAKER();

PyCodeObject *code = _PyFrame_GetCode(frame);
_PyExecutorObject *executor = code->co_executors->executors[oparg & 255];
if (executor->vm_data.valid) {
Py_INCREF(executor);
current_executor = executor;
GOTO_TIER_TWO();
}
else {
/* ENTER_EXECUTOR will be the first code unit of the instruction */
assert(oparg < 256);
code->co_executors->executors[oparg] = NULL;
opcode = this_instr->op.code = executor->vm_data.opcode;
this_instr->op.arg = executor->vm_data.oparg;
oparg = executor->vm_data.oparg;
Py_DECREF(executor);
next_instr = this_instr;
DISPATCH_GOTO();
}
current_executor = code->co_executors->executors[oparg & 255];
assert(current_executor->vm_data.index == INSTR_OFFSET() - 1);
assert(current_executor->vm_data.code == code);
assert(current_executor->vm_data.valid);
Py_INCREF(current_executor);
GOTO_TIER_TWO();
}

replaced op(_POP_JUMP_IF_FALSE, (cond -- )) {
Expand Down
20 changes: 19 additions & 1 deletion Python/clinic/sysmodule.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 7 additions & 18 deletions Python/generated_cases.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

64 changes: 35 additions & 29 deletions Python/optimizer.c
Original file line number Diff line number Diff line change
Expand Up @@ -73,25 +73,21 @@ insert_executor(PyCodeObject *code, _Py_CODEUNIT *instr, int index, _PyExecutorO
Py_INCREF(executor);
if (instr->op.code == ENTER_EXECUTOR) {
assert(index == instr->op.arg);
_PyExecutorObject *old = code->co_executors->executors[index];
executor->vm_data.opcode = old->vm_data.opcode;
executor->vm_data.oparg = old->vm_data.oparg;
old->vm_data.opcode = 0;
code->co_executors->executors[index] = executor;
Py_DECREF(old);
_Py_ExecutorClear(code->co_executors->executors[index]);
}
else {
assert(code->co_executors->size == index);
assert(code->co_executors->capacity > index);
executor->vm_data.opcode = instr->op.code;
executor->vm_data.oparg = instr->op.arg;
code->co_executors->executors[index] = executor;
assert(index < MAX_EXECUTORS_SIZE);
instr->op.code = ENTER_EXECUTOR;
instr->op.arg = index;
code->co_executors->size++;
}
return;
executor->vm_data.opcode = instr->op.code;
executor->vm_data.oparg = instr->op.arg;
executor->vm_data.code = code;
executor->vm_data.index = (int)(instr - _PyCode_CODE(code));
code->co_executors->executors[index] = executor;
assert(index < MAX_EXECUTORS_SIZE);
instr->op.code = ENTER_EXECUTOR;
instr->op.arg = index;
}

int
Expand Down Expand Up @@ -1071,15 +1067,15 @@ link_executor(_PyExecutorObject *executor)
}
head->vm_data.links.next = executor;
}
executor->vm_data.linked = true;
executor->vm_data.valid = true;
/* executor_list_head must be first in list */
assert(interp->executor_list_head->vm_data.links.previous == NULL);
}

static void
unlink_executor(_PyExecutorObject *executor)
{
if (!executor->vm_data.linked) {
if (!executor->vm_data.valid) {
return;
}
_PyExecutorLinkListNode *links = &executor->vm_data.links;
Expand All @@ -1097,7 +1093,7 @@ unlink_executor(_PyExecutorObject *executor)
assert(interp->executor_list_head == executor);
interp->executor_list_head = next;
}
executor->vm_data.linked = false;
executor->vm_data.valid = false;
}

/* This must be called by optimizers before using the executor */
Expand All @@ -1116,12 +1112,24 @@ void
_Py_ExecutorClear(_PyExecutorObject *executor)
{
unlink_executor(executor);
PyCodeObject *code = executor->vm_data.code;
if (code == NULL) {
return;
}
_Py_CODEUNIT *instruction = &_PyCode_CODE(code)[executor->vm_data.index];
assert(instruction->op.code == ENTER_EXECUTOR);
int index = instruction->op.arg;
assert(code->co_executors->executors[index] == executor);
instruction->op.code = executor->vm_data.opcode;
instruction->op.arg = executor->vm_data.oparg;
executor->vm_data.code = NULL;
Py_CLEAR(code->co_executors->executors[index]);
}

void
_Py_Executor_DependsOn(_PyExecutorObject *executor, void *obj)
{
assert(executor->vm_data.valid = true);
assert(executor->vm_data.valid);
_Py_BloomFilter_Add(&executor->vm_data.bloom, obj);
}

Expand All @@ -1140,8 +1148,7 @@ _Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj)
assert(exec->vm_data.valid);
_PyExecutorObject *next = exec->vm_data.links.next;
if (bloom_filter_may_contain(&exec->vm_data.bloom, &obj_filter)) {
exec->vm_data.valid = false;
unlink_executor(exec);
_Py_ExecutorClear(exec);
}
exec = next;
}
Expand All @@ -1151,15 +1158,14 @@ _Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj)
void
_Py_Executors_InvalidateAll(PyInterpreterState *interp)
{
/* Walk the list of executors */
for (_PyExecutorObject *exec = interp->executor_list_head; exec != NULL;) {
assert(exec->vm_data.valid);
_PyExecutorObject *next = exec->vm_data.links.next;
exec->vm_data.links.next = NULL;
exec->vm_data.links.previous = NULL;
exec->vm_data.valid = false;
exec->vm_data.linked = false;
exec = next;
while (interp->executor_list_head) {
_PyExecutorObject *executor = interp->executor_list_head;
if (executor->vm_data.code) {
// Clear the entire code object so its co_executors array be freed:
_PyCode_Clear_Executors(executor->vm_data.code);
}
else {
_Py_ExecutorClear(executor);
}
}
interp->executor_list_head = NULL;
}
17 changes: 17 additions & 0 deletions Python/sysmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -2127,6 +2127,22 @@ sys__clear_type_cache_impl(PyObject *module)
Py_RETURN_NONE;
}

/*[clinic input]
sys._clear_internal_caches
Clear all internal performance-related caches.
[clinic start generated code]*/

static PyObject *
sys__clear_internal_caches_impl(PyObject *module)
/*[clinic end generated code: output=0ee128670a4966d6 input=253e741ca744f6e8]*/
{
PyInterpreterState *interp = _PyInterpreterState_GET();
_Py_Executors_InvalidateAll(interp);
PyType_ClearCache();
Py_RETURN_NONE;
}

/* Note that, for now, we do not have a per-interpreter equivalent
for sys.is_finalizing(). */

Expand Down Expand Up @@ -2461,6 +2477,7 @@ static PyMethodDef sys_methods[] = {
{"audit", _PyCFunction_CAST(sys_audit), METH_FASTCALL, audit_doc },
{"breakpointhook", _PyCFunction_CAST(sys_breakpointhook),
METH_FASTCALL | METH_KEYWORDS, breakpointhook_doc},
SYS__CLEAR_INTERNAL_CACHES_METHODDEF
SYS__CLEAR_TYPE_CACHE_METHODDEF
SYS__CURRENT_FRAMES_METHODDEF
SYS__CURRENT_EXCEPTIONS_METHODDEF
Expand Down

0 comments on commit 235cacf

Please sign in to comment.