Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
14 changes: 7 additions & 7 deletions Include/internal/pycore_backoff.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ extern "C" {
#include <assert.h>
#include <stdbool.h>
#include "pycore_structs.h" // _Py_BackoffCounter
#include "pycore_tstate.h" // _PyPolicy
#include "pycore_interp_structs.h" // _PyOptimizationConfig

/* 16-bit countdown counters using exponential backoff.

Expand Down Expand Up @@ -128,11 +128,11 @@ trigger_backoff_counter(void)
#define JUMP_BACKWARD_INITIAL_VALUE 4000
#define JUMP_BACKWARD_INITIAL_BACKOFF 6
static inline _Py_BackoffCounter
initial_jump_backoff_counter(_PyPolicy *policy)
initial_jump_backoff_counter(_PyOptimizationConfig *opt_config)
{
return make_backoff_counter(
policy->interp.jump_backward_initial_value,
policy->interp.jump_backward_initial_backoff);
opt_config->jump_backward_initial_value,
opt_config->jump_backward_initial_backoff);
}

/* Initial exit temperature.
Expand All @@ -143,11 +143,11 @@ initial_jump_backoff_counter(_PyPolicy *policy)
#define SIDE_EXIT_INITIAL_BACKOFF 6

static inline _Py_BackoffCounter
initial_temperature_backoff_counter(_PyPolicy *policy)
initial_temperature_backoff_counter(_PyOptimizationConfig *opt_config)
{
return make_backoff_counter(
policy->jit.side_exit_initial_value,
policy->jit.side_exit_initial_backoff);
opt_config->side_exit_initial_value,
opt_config->side_exit_initial_backoff);
}

/* Unreachable backoff counter. */
Expand Down
18 changes: 18 additions & 0 deletions Include/internal/pycore_interp_structs.h
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,21 @@ typedef struct _rare_events {
uint8_t func_modification;
} _rare_events;

// Optimization configuration for the interpreter.
// This groups all thresholds and optimization flags for both JIT and interpreter.
typedef struct _PyOptimizationConfig {
// Interpreter optimization thresholds
uint16_t jump_backward_initial_value;
uint16_t jump_backward_initial_backoff;

// JIT optimization thresholds
uint16_t side_exit_initial_value;
uint16_t side_exit_initial_backoff;

// Optimization flags
bool specialization_enabled;
} _PyOptimizationConfig;

struct
Bigint {
struct Bigint *next;
Expand Down Expand Up @@ -945,6 +960,9 @@ struct _is {
PyObject *common_consts[NUM_COMMON_CONSTANTS];
bool jit;
bool compiling;

// Optimization configuration (thresholds and flags for JIT and interpreter)
_PyOptimizationConfig opt_config;
struct _PyExecutorObject *executor_list_head;
struct _PyExecutorObject *executor_deletion_list_head;
struct _PyExecutorObject *cold_executor;
Expand Down
16 changes: 0 additions & 16 deletions Include/internal/pycore_tstate.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,21 +62,6 @@ typedef struct _PyJitTracerState {

#endif

typedef struct _PyJitPolicy {
uint16_t side_exit_initial_value;
uint16_t side_exit_initial_backoff;
} _PyJitPolicy;

typedef struct _PyInterpreterPolicy {
uint16_t jump_backward_initial_value;
uint16_t jump_backward_initial_backoff;
} _PyInterpreterPolicy;

typedef struct _PyPolicy {
_PyJitPolicy jit;
_PyInterpreterPolicy interp;
} _PyPolicy;

// Every PyThreadState is actually allocated as a _PyThreadStateImpl. The
// PyThreadState fields are exposed as part of the C API, although most fields
// are intended to be private. The _PyThreadStateImpl fields not exposed.
Expand Down Expand Up @@ -155,7 +140,6 @@ typedef struct _PyThreadStateImpl {
#if _Py_TIER2
_PyJitTracerState *jit_tracer_state;
#endif
_PyPolicy policy;
} _PyThreadStateImpl;

#ifdef __cplusplus
Expand Down
16 changes: 9 additions & 7 deletions Objects/codeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -580,9 +580,10 @@ init_code(PyCodeObject *co, struct _PyCodeConstructor *con)
}
co->_co_firsttraceable = entry_point;
#ifdef Py_GIL_DISABLED
_PyCode_Quicken(_PyCode_CODE(co), Py_SIZE(co), interp->config.tlbc_enabled);
int enable_counters = interp->config.tlbc_enabled && interp->opt_config.specialization_enabled;
_PyCode_Quicken(_PyCode_CODE(co), Py_SIZE(co), enable_counters);
#else
_PyCode_Quicken(_PyCode_CODE(co), Py_SIZE(co), 1);
_PyCode_Quicken(_PyCode_CODE(co), Py_SIZE(co), interp->opt_config.specialization_enabled);
#endif
notify_code_watchers(PY_CODE_EVENT_CREATE, co);
return 0;
Expand Down Expand Up @@ -3369,13 +3370,13 @@ deopt_code_unit(PyCodeObject *code, int i)
}

static void
copy_code(_Py_CODEUNIT *dst, PyCodeObject *co)
copy_code(PyInterpreterState *interp, _Py_CODEUNIT *dst, PyCodeObject *co)
{
int code_len = (int) Py_SIZE(co);
for (int i = 0; i < code_len; i += _PyInstruction_GetLength(co, i)) {
dst[i] = deopt_code_unit(co, i);
}
_PyCode_Quicken(dst, code_len, 1);
_PyCode_Quicken(dst, code_len, interp->opt_config.specialization_enabled);
}

static Py_ssize_t
Expand All @@ -3391,7 +3392,7 @@ get_pow2_greater(Py_ssize_t initial, Py_ssize_t limit)
}

static _Py_CODEUNIT *
create_tlbc_lock_held(PyCodeObject *co, Py_ssize_t idx)
create_tlbc_lock_held(PyInterpreterState *interp, PyCodeObject *co, Py_ssize_t idx)
{
_PyCodeArray *tlbc = co->co_tlbc;
if (idx >= tlbc->size) {
Expand All @@ -3414,7 +3415,7 @@ create_tlbc_lock_held(PyCodeObject *co, Py_ssize_t idx)
PyErr_NoMemory();
return NULL;
}
copy_code((_Py_CODEUNIT *) bc, co);
copy_code(interp, (_Py_CODEUNIT *) bc, co);
assert(tlbc->entries[idx] == NULL);
tlbc->entries[idx] = bc;
return (_Py_CODEUNIT *) bc;
Expand All @@ -3429,7 +3430,8 @@ get_tlbc_lock_held(PyCodeObject *co)
if (idx < tlbc->size && tlbc->entries[idx] != NULL) {
return (_Py_CODEUNIT *)tlbc->entries[idx];
}
return create_tlbc_lock_held(co, idx);
PyInterpreterState *interp = tstate->base.interp;
return create_tlbc_lock_held(interp, co, idx);
}

_Py_CODEUNIT *
Expand Down
4 changes: 2 additions & 2 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -1472,7 +1472,7 @@ stop_tracing_and_jit(PyThreadState *tstate, _PyInterpreterFrame *frame)
tracer->initial_state.jump_backward_instr[1].counter = restart_backoff_counter(counter);
}
else {
tracer->initial_state.jump_backward_instr[1].counter = initial_jump_backoff_counter(&_tstate->policy);
tracer->initial_state.jump_backward_instr[1].counter = initial_jump_backoff_counter(&tstate->interp->opt_config);
}
}
else {
Expand All @@ -1482,7 +1482,7 @@ stop_tracing_and_jit(PyThreadState *tstate, _PyInterpreterFrame *frame)
exit->temperature = restart_backoff_counter(exit->temperature);
}
else {
exit->temperature = initial_temperature_backoff_counter(&_tstate->policy);
exit->temperature = initial_temperature_backoff_counter(&tstate->interp->opt_config);
}
}
_PyJit_FinalizeTracing(tstate);
Expand Down
3 changes: 2 additions & 1 deletion Python/optimizer.c
Original file line number Diff line number Diff line change
Expand Up @@ -1351,9 +1351,10 @@ make_executor_from_uops(_PyThreadStateImpl *tstate, _PyUOpInstruction *buffer, i
_PyExecutorObject *cold = _PyExecutor_GetColdExecutor();
_PyExecutorObject *cold_dynamic = _PyExecutor_GetColdDynamicExecutor();
cold->vm_data.chain_depth = chain_depth;
PyInterpreterState *interp = tstate->base.interp;
for (int i = 0; i < exit_count; i++) {
executor->exits[i].index = i;
executor->exits[i].temperature = initial_temperature_backoff_counter(&tstate->policy);
executor->exits[i].temperature = initial_temperature_backoff_counter(&interp->opt_config);
}
int next_exit = exit_count-1;
_PyUOpInstruction *dest = (_PyUOpInstruction *)&executor->trace[length];
Expand Down
69 changes: 41 additions & 28 deletions Python/pystate.c
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,21 @@ _Py_LazyJitShim(
main interpreter. We fix those fields here, in addition
to the other dynamically initialized fields.
*/

static inline void
init_policy(uint16_t *target, const char *env_name, uint16_t default_value,
long min_value, long max_value)
{
*target = default_value;
char *env = Py_GETENV(env_name);
if (env && *env != '\0') {
long value = atol(env);
if (value >= min_value && value <= max_value) {
*target = (uint16_t)value;
}
}
}

static PyStatus
init_interpreter(PyInterpreterState *interp,
_PyRuntimeState *runtime, int64_t id,
Expand Down Expand Up @@ -572,6 +587,31 @@ init_interpreter(PyInterpreterState *interp,
interp->executor_list_head = NULL;
interp->executor_deletion_list_head = NULL;
interp->executor_creation_counter = JIT_CLEANUP_THRESHOLD;

// Initialize optimization configuration from environment variables
init_policy(&interp->opt_config.jump_backward_initial_value,
"PYTHON_JIT_JUMP_BACKWARD_INITIAL_VALUE",
JUMP_BACKWARD_INITIAL_VALUE, 1, MAX_VALUE);
init_policy(&interp->opt_config.jump_backward_initial_backoff,
"PYTHON_JIT_JUMP_BACKWARD_INITIAL_BACKOFF",
JUMP_BACKWARD_INITIAL_BACKOFF, 0, MAX_BACKOFF);
#ifdef _Py_TIER2
init_policy(&interp->opt_config.side_exit_initial_value,
"PYTHON_JIT_SIDE_EXIT_INITIAL_VALUE",
SIDE_EXIT_INITIAL_VALUE, 1, MAX_VALUE);
init_policy(&interp->opt_config.side_exit_initial_backoff,
"PYTHON_JIT_SIDE_EXIT_INITIAL_BACKOFF",
SIDE_EXIT_INITIAL_BACKOFF, 0, MAX_BACKOFF);
Comment on lines +598 to +619
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think Mark wants these numbers to be automatic, and remove the current env variables.

So for example, PYHTON_JIT_STRESS would set PYTHON_JIT_JUMP_BACKWARD_INITIAL_VALUE to 63, and PYTHON_JIT_SIDE_EXIT_INITIAL_VALUE to 63 a well.

#endif

// Check if specialization should be disabled
// If PYTHON_SPECIALIZATION_OFF is set to any non-empty value, disable specialization
char *spec_off_env = Py_GETENV("PYTHON_SPECIALIZATION_OFF");
Copy link
Copy Markdown
Member Author

@corona10 corona10 Jan 10, 2026

Choose a reason for hiding this comment

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

@Fidget-Spinner We can define about PYTHON_SPECIALIZATION_OFF very easily and maybe PYTHON_JIT_OFF too.
But about PYHTON_JIT_STRESS=1, we need to discuss which numbers we will use.

if (spec_off_env && *spec_off_env != '\0' && *spec_off_env != '0') {
interp->opt_config.specialization_enabled = false;
} else {
interp->opt_config.specialization_enabled = true;
}
if (interp != &runtime->_main_interpreter) {
/* Fix the self-referential, statically initialized fields. */
interp->dtoa = (struct _dtoa_state)_dtoa_state_INIT(interp);
Expand Down Expand Up @@ -1439,20 +1479,6 @@ decref_threadstate(_PyThreadStateImpl *tstate)
}
}

static inline void
init_policy(uint16_t *target, const char *env_name, uint16_t default_value,
long min_value, long max_value)
{
*target = default_value;
char *env = Py_GETENV(env_name);
if (env && *env != '\0') {
long value = atol(env);
if (value >= min_value && value <= max_value) {
*target = (uint16_t)value;
}
}
}

/* Get the thread state to a minimal consistent state.
Further init happens in pylifecycle.c before it can be used.
All fields not initialized here are expected to be zeroed out,
Expand Down Expand Up @@ -1538,21 +1564,8 @@ init_threadstate(_PyThreadStateImpl *_tstate,

_tstate->asyncio_running_loop = NULL;
_tstate->asyncio_running_task = NULL;
// Initialize interpreter policy from environment variables
init_policy(&_tstate->policy.interp.jump_backward_initial_value,
"PYTHON_JIT_JUMP_BACKWARD_INITIAL_VALUE",
JUMP_BACKWARD_INITIAL_VALUE, 1, MAX_VALUE);
init_policy(&_tstate->policy.interp.jump_backward_initial_backoff,
"PYTHON_JIT_JUMP_BACKWARD_INITIAL_BACKOFF",
JUMP_BACKWARD_INITIAL_BACKOFF, 0, MAX_BACKOFF);

#ifdef _Py_TIER2
// Initialize JIT policy from environment variables
init_policy(&_tstate->policy.jit.side_exit_initial_value,
"PYTHON_JIT_SIDE_EXIT_INITIAL_VALUE",
SIDE_EXIT_INITIAL_VALUE, 1, MAX_VALUE);
init_policy(&_tstate->policy.jit.side_exit_initial_backoff,
"PYTHON_JIT_SIDE_EXIT_INITIAL_BACKOFF",
SIDE_EXIT_INITIAL_BACKOFF, 0, MAX_BACKOFF);
_tstate->jit_tracer_state = NULL;
#endif
tstate->delete_later = NULL;
Expand Down
4 changes: 2 additions & 2 deletions Python/specialize.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ _PyCode_Quicken(_Py_CODEUNIT *instructions, Py_ssize_t size, int enable_counters
_Py_BackoffCounter jump_counter, adaptive_counter;
if (enable_counters) {
PyThreadState *tstate = _PyThreadState_GET();
_PyThreadStateImpl *tstate_impl = (_PyThreadStateImpl *)tstate;
jump_counter = initial_jump_backoff_counter(&tstate_impl->policy);
PyInterpreterState *interp = tstate->interp;
jump_counter = initial_jump_backoff_counter(&interp->opt_config);
adaptive_counter = adaptive_counter_warmup();
}
else {
Expand Down
Loading