Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GH-123516: Improve JIT memory consumption by invalidating cold executors #124443

Open
wants to merge 74 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
0eac77b
resolve conflict
savannahostrowski Jul 26, 2024
d576296
tests pass except ssl
savannahostrowski Jun 25, 2024
68e95d6
remove file
savannahostrowski Jul 26, 2024
c903af4
this is broken
savannahostrowski Aug 12, 2024
5ca6b7f
gc approach
savannahostrowski Aug 12, 2024
beb4f65
rebase
savannahostrowski Aug 12, 2024
427dbf5
Update has_run to run_count
savannahostrowski Aug 13, 2024
92d5590
update initialized run_count and move invalidate old
savannahostrowski Aug 13, 2024
0cdf638
set threshold to 1`
savannahostrowski Aug 13, 2024
58e7447
move incrementing run count into a new op
savannahostrowski Aug 14, 2024
6c047e4
add invalidation threshold in gc of 10
savannahostrowski Aug 20, 2024
2645023
move back to incremenet
savannahostrowski Aug 20, 2024
7c7ae98
remove print
savannahostrowski Aug 20, 2024
6d6d306
move invalidation to executor creation
savannahostrowski Aug 27, 2024
4d086fe
change threshold
savannahostrowski Aug 27, 2024
d08e45a
new line
savannahostrowski Aug 27, 2024
6315877
update constant
savannahostrowski Aug 27, 2024
622c266
📜🤖 Added by blurb_it.
blurb-it[bot] Aug 27, 2024
e5117b2
Merge branch 'main' into jit-mem-invalidate-10
savannahostrowski Aug 29, 2024
7c6704c
resolve conflict
savannahostrowski Jul 26, 2024
1d72fdd
tests pass except ssl
savannahostrowski Jun 25, 2024
1778185
remove file
savannahostrowski Jul 26, 2024
2506821
this is broken
savannahostrowski Aug 12, 2024
fca6dec
gc approach
savannahostrowski Aug 12, 2024
29436fd
rebase
savannahostrowski Aug 12, 2024
b969b11
Update has_run to run_count
savannahostrowski Aug 13, 2024
a669e0f
update initialized run_count and move invalidate old
savannahostrowski Aug 13, 2024
deb73ec
set threshold to 1`
savannahostrowski Aug 13, 2024
9fa55e8
move incrementing run count into a new op
savannahostrowski Aug 14, 2024
e4a461a
add invalidation threshold in gc of 10
savannahostrowski Aug 20, 2024
d5a2bed
move back to incremenet
savannahostrowski Aug 20, 2024
d232e63
remove print
savannahostrowski Aug 20, 2024
e4a456a
move invalidation to executor creation
savannahostrowski Aug 27, 2024
b7d2d5a
change threshold
savannahostrowski Aug 27, 2024
6dcd2dc
new line
savannahostrowski Aug 27, 2024
219f890
update constant
savannahostrowski Aug 27, 2024
fb7b04d
📜🤖 Added by blurb_it.
blurb-it[bot] Aug 27, 2024
ea397a3
Merge branch 'jit-mem-invalidate-10' of https://github.com/savannahos…
savannahostrowski Aug 29, 2024
310d20c
address pr comments
savannahostrowski Aug 30, 2024
d2f9dc4
refactor invalidatecold
savannahostrowski Aug 30, 2024
d755d56
refactor to was_run bool
savannahostrowski Aug 30, 2024
c9534c0
update blurb
savannahostrowski Aug 30, 2024
61cd3c5
Merge branch 'main' into jit-mem-invalidate-10
savannahostrowski Aug 30, 2024
e5065ad
Update op name to be more reflective of was_run
savannahostrowski Aug 30, 2024
2d09259
fix typo
savannahostrowski Aug 30, 2024
d2e8e29
rename uop
savannahostrowski Aug 30, 2024
a894598
dedent and initialize executors_created
savannahostrowski Aug 31, 2024
758ee03
Merge branch 'main' into jit-mem-invalidate-10
savannahostrowski Sep 1, 2024
1927bfe
address some PR comments
savannahostrowski Sep 1, 2024
8ee0d7f
Update Python/optimizer.c
savannahostrowski Sep 1, 2024
cedd65d
Update Python/optimizer.c
savannahostrowski Sep 1, 2024
0a9b5b6
make was_run uint`
savannahostrowski Sep 1, 2024
180a68e
add comment for JIT_CLEANUP_THRESHOLD
savannahostrowski Sep 1, 2024
7cb9cba
Remove extraneous reset of executors_created
savannahostrowski Sep 1, 2024
8939ecf
Merge branch 'main' into jit-mem-invalidate-10
savannahostrowski Sep 3, 2024
00f03d2
Merge branch 'main' into jit-mem-invalidate-10
savannahostrowski Sep 3, 2024
4981ab2
Merge branch 'jit-mem-invalidate-10' of https://github.com/savannahos…
savannahostrowski Sep 3, 2024
3c59316
condense conditional statements
savannahostrowski Sep 3, 2024
306c3c3
Merge branch 'main' into jit-mem-invalidate-10
savannahostrowski Sep 10, 2024
fe50615
Address PR comments from Brandt and Mark
savannahostrowski Sep 13, 2024
d51817b
Merge branch 'main' into jit-mem-invalidate-10
savannahostrowski Sep 13, 2024
131618a
Merge branch 'main' into jit-mem-invalidate-10
savannahostrowski Sep 17, 2024
5961103
Refactor to use eval breaker
savannahostrowski Sep 17, 2024
99262b6
Address comments
savannahostrowski Sep 19, 2024
230fe05
Update to 10k
savannahostrowski Sep 19, 2024
f3c01a1
Update to 1m
savannahostrowski Sep 19, 2024
563a4d7
update cases
savannahostrowski Sep 19, 2024
062c54f
add py_set_eval_breaker_bit to nonescaping'
savannahostrowski Sep 23, 2024
17ece50
create 100k branch
savannahostrowski Sep 23, 2024
beea8c6
Merge branch 'main' into jit-inv-mem-100k
savannahostrowski Sep 24, 2024
eb48f82
Merge branch 'main' into jit-inv-mem-100k
savannahostrowski Sep 24, 2024
34363f2
Address comments from Brandt
savannahostrowski Sep 24, 2024
77e81d4
Merge branch 'main' into jit-inv-mem-100k
savannahostrowski Sep 25, 2024
09e3300
Dedent goto error
savannahostrowski Sep 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Include/internal/pycore_ceval.h
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ PyAPI_FUNC(PyObject *) _PyEval_LoadName(PyThreadState *tstate, _PyInterpreterFra
#define _PY_GC_SCHEDULED_BIT (1U << 4)
#define _PY_EVAL_PLEASE_STOP_BIT (1U << 5)
#define _PY_EVAL_EXPLICIT_MERGE_BIT (1U << 6)
#define _PY_EVAL_JIT_INVALIDATE_COLD_BIT (1U << 7)

/* Reserve a few bits for future use */
#define _PY_EVAL_EVENTS_BITS 8
Expand Down
2 changes: 1 addition & 1 deletion Include/internal/pycore_interp.h
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ struct _is {
struct callable_cache callable_cache;
_PyOptimizerObject *optimizer;
_PyExecutorObject *executor_list_head;

size_t trace_run_counter;
_rare_events rare_events;
PyDict_WatchCallback builtins_dict_watcher;

Expand Down
14 changes: 11 additions & 3 deletions Include/internal/pycore_optimizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ typedef struct {
typedef struct {
uint8_t opcode;
uint8_t oparg;
uint16_t valid:1;
uint16_t linked:1;
uint16_t chain_depth:14; // Must be big engough for MAX_CHAIN_DEPTH - 1.
uint8_t valid:1;
uint8_t linked:1;
uint8_t chain_depth:6; // Must be big enough for MAX_CHAIN_DEPTH - 1.
bool warm;
int index; // Index of ENTER_EXECUTOR (if code isn't NULL, below).
_PyBloomFilter bloom;
_PyExecutorLinkListNode links;
Expand Down Expand Up @@ -123,11 +124,18 @@ PyAPI_FUNC(PyObject *) _PyOptimizer_NewUOpOptimizer(void);
#ifdef _Py_TIER2
PyAPI_FUNC(void) _Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj, int is_invalidation);
PyAPI_FUNC(void) _Py_Executors_InvalidateAll(PyInterpreterState *interp, int is_invalidation);
PyAPI_FUNC(void) _Py_Executors_InvalidateCold(PyInterpreterState *interp);

#else
# define _Py_Executors_InvalidateDependency(A, B, C) ((void)0)
# define _Py_Executors_InvalidateAll(A, B) ((void)0)
# define _Py_Executors_InvalidateCold(A) ((void)0)

#endif

// Used as the threshold to trigger executor invalidation when
// trace_run_counter is greater than this value.
#define JIT_CLEANUP_THRESHOLD 100000

// This is the length of the trace we project initially.
#define UOP_MAX_TRACE_LENGTH 800
Expand Down
71 changes: 36 additions & 35 deletions Include/internal/pycore_uop_ids.h

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

4 changes: 4 additions & 0 deletions Include/internal/pycore_uop_metadata.h

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Improved JIT memory consumption by periodically freeing memory used by infrequently-executed code.
This change is especially likely to improve the memory footprint of long-running programs.
8 changes: 8 additions & 0 deletions Python/bytecodes.c
Original file line number Diff line number Diff line change
Expand Up @@ -4828,6 +4828,14 @@ dummy_func(
assert(((_PyExecutorObject *)executor)->vm_data.valid);
}

tier2 op(_MAKE_WARM, (--)) {
current_executor->vm_data.warm = true;
if (++tstate->interp->trace_run_counter > JIT_CLEANUP_THRESHOLD) {
_Py_set_eval_breaker_bit(tstate, _PY_EVAL_JIT_INVALIDATE_COLD_BIT);
tstate->interp->trace_run_counter = 0;
}
}

tier2 op(_FATAL_ERROR, (--)) {
assert(0);
Py_FatalError("Fatal error uop executed.");
Expand Down
5 changes: 5 additions & 0 deletions Python/ceval_gil.c
Original file line number Diff line number Diff line change
Expand Up @@ -1289,6 +1289,11 @@ _Py_HandlePending(PyThreadState *tstate)
_Py_RunGC(tstate);
}

if ((breaker & _PY_EVAL_JIT_INVALIDATE_COLD_BIT) != 0) {
_Py_unset_eval_breaker_bit(tstate, _PY_EVAL_JIT_INVALIDATE_COLD_BIT);
_Py_Executors_InvalidateCold(tstate->interp);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a thought I had while reading through... I don't think any of the stuff that manipulates the linked list of executors is thread-safe currently. Probably not a problem for this PR, but in the future we'll probably want to go through and add _PyEval_StopTheWorld and _PyEval_StartTheWorld calls in optimize.c.

}

/* GIL drop request */
if ((breaker & _PY_GIL_DROP_REQUEST_BIT) != 0) {
/* Give another thread a chance */
Expand Down
9 changes: 9 additions & 0 deletions Python/executor_cases.c.h

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

42 changes: 42 additions & 0 deletions Python/optimizer.c
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,7 @@ translate_bytecode_to_trace(
code->co_firstlineno,
2 * INSTR_IP(initial_instr, code));
ADD_TO_TRACE(_START_EXECUTOR, 0, (uintptr_t)instr, INSTR_IP(instr, code));
ADD_TO_TRACE(_MAKE_WARM, 0, 0, 0);
uint32_t target = 0;

for (;;) {
Expand Down Expand Up @@ -1194,6 +1195,9 @@ make_executor_from_uops(_PyUOpInstruction *buffer, int length, const _PyBloomFil
executor->jit_code = NULL;
executor->jit_side_entry = NULL;
executor->jit_size = 0;
// This is initialized to true so we can prevent the executor
// from being immediately detected as cold and invalidated.
executor->vm_data.warm = true;
if (_PyJIT_Compile(executor, executor->trace, length)) {
Py_DECREF(executor);
return NULL;
Expand Down Expand Up @@ -1659,4 +1663,42 @@ _Py_Executors_InvalidateAll(PyInterpreterState *interp, int is_invalidation)
}
}

void
_Py_Executors_InvalidateCold(PyInterpreterState *interp)
{
/* Walk the list of executors */
/* TO DO -- Use a tree to avoid traversing as many objects */
PyObject *invalidate = PyList_New(0);
if (invalidate == NULL) {
goto error;
}

/* Clearing an executor can deallocate others, so we need to make a list of
* executors to invalidate first */
for (_PyExecutorObject *exec = interp->executor_list_head; exec != NULL;) {
assert(exec->vm_data.valid);
_PyExecutorObject *next = exec->vm_data.links.next;

if (!exec->vm_data.warm && PyList_Append(invalidate, (PyObject *)exec) < 0) {
goto error;
}
else {
exec->vm_data.warm = false;
}

exec = next;
}
for (Py_ssize_t i = 0; i < PyList_GET_SIZE(invalidate); i++) {
_PyExecutorObject *exec = (_PyExecutorObject *)PyList_GET_ITEM(invalidate, i);
executor_clear(exec);
}
Py_DECREF(invalidate);
return;
error:
PyErr_Clear();
Py_XDECREF(invalidate);
// If we're truly out of memory, wiping out everything is a fine fallback
_Py_Executors_InvalidateAll(interp, 0);
}

#endif /* _Py_TIER2 */
4 changes: 4 additions & 0 deletions Python/optimizer_cases.c.h

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

1 change: 1 addition & 0 deletions Python/pystate.c
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,7 @@ init_interpreter(PyInterpreterState *interp,
#ifdef _Py_TIER2
(void)_Py_SetOptimizer(interp, NULL);
interp->executor_list_head = NULL;
interp->trace_run_counter = 0;
#endif
if (interp != &runtime->_main_interpreter) {
/* Fix the self-referential, statically initialized fields. */
Expand Down
1 change: 1 addition & 0 deletions Tools/cases_generator/analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,7 @@ def has_error_without_pop(op: parser.InstDef) -> bool:
"_PyList_FromStackRefSteal",
"_PyTuple_FromArraySteal",
"_PyTuple_FromStackRefSteal",
"_Py_set_eval_breaker_bit"
)

ESCAPING_FUNCTIONS = (
Expand Down
Loading